diff --git a/.github/workflows/appsec.yml b/.github/workflows/appsec.yml index 1276ecff2d..f913f17fdf 100644 --- a/.github/workflows/appsec.yml +++ b/.github/workflows/appsec.yml @@ -90,12 +90,12 @@ jobs: macos: name: ${{ matrix.runs-on }} go${{ matrix.go-version }} - runs-on: macos-11 # oldest macos runner available - the full macOS matrix is in go-libddwaf + runs-on: ${{ matrix.runs-on }} needs: go-mod-caching strategy: matrix: - runs-on: [ macos-11, macos-14 ] # oldest and newest macos runners available - macos-14 mainly is here to cover the fact it is an ARM machine - go-version: [ "1.22", "1.21", "1.20" ] + runs-on: [ macos-12, macos-14 ] # oldest and newest macos runners available - macos-14 mainly is here to cover the fact it is an ARM machine + go-version: [ "1.22", "1.21" ] fail-fast: true # saving some CI time - macos runners too long to get steps: - uses: actions/checkout@v4 @@ -124,9 +124,6 @@ jobs: run: | set -euxo pipefail cgocheck="GOEXPERIMENT=cgocheck2" - if [[ "$(go version)" =~ go1.20 ]]; then - cgocheck="GODEBUG=cgocheck=2" - fi for cgo in "0" "1"; do for appsec_enabled_env in "" "DD_APPSEC_ENABLED=true" "DD_APPSEC_ENABLED=false"; do for cgocheck_env in "" "$cgocheck"; do @@ -190,14 +187,9 @@ jobs: needs: go-mod-caching strategy: matrix: - go-version: [ "1.22", "1.21", "1.20" ] - distribution: [ bookworm, bullseye, buster, alpine ] + go-version: [ "1.22", "1.21" ] + distribution: [ bookworm, bullseye, alpine ] platform: [ linux/amd64, linux/arm64 ] - exclude: - - go-version: "1.21" - distribution: buster - - go-version: "1.22" - distribution: buster fail-fast: false steps: @@ -266,6 +258,7 @@ jobs: test-app-smoke-tests: name: Smoke Tests + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner == 'DataDog' uses: DataDog/appsec-go-test-app/.github/workflows/smoke-tests.yml@main with: dd-trace-go-version: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} diff --git a/.github/workflows/datadog-static-analysis.yml b/.github/workflows/datadog-static-analysis.yml new file mode 100644 index 0000000000..8094914c28 --- /dev/null +++ b/.github/workflows/datadog-static-analysis.yml @@ -0,0 +1,21 @@ +on: [push] + +name: Datadog Static Analysis + +jobs: + static-analysis: + runs-on: ubuntu-latest + name: Datadog Static Analyzer + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Check code meets quality and security standards + id: datadog-static-analysis + uses: DataDog/datadog-static-analyzer-github-action@v1 + with: + dd_api_key: ${{ secrets.STATIC_ANALYZER_API_KEY }} + dd_app_key: ${{ secrets.STATIC_ANALYZER_APP_KEY }} + dd_service: dd-trace-go + dd_env: ci + dd_site: datadoghq.com + cpu_count: 2 diff --git a/.github/workflows/main-branch-tests.yml b/.github/workflows/main-branch-tests.yml index b681aaa9b7..96e4c7ea25 100644 --- a/.github/workflows/main-branch-tests.yml +++ b/.github/workflows/main-branch-tests.yml @@ -22,7 +22,7 @@ jobs: unit-integration-tests: strategy: matrix: - go-version: ["1.20", "1.21", "1.22"] + go-version: [ "1.21", "1.22" ] fail-fast: false uses: ./.github/workflows/unit-integration-tests.yml with: @@ -33,7 +33,7 @@ jobs: strategy: matrix: runs-on: [ macos-latest, windows-latest, ubuntu-latest ] - go-version: ["1.20", "1.21", "1.22"] + go-version: [ "1.21", "1.22" ] fail-fast: false uses: ./.github/workflows/multios-unit-tests.yml with: diff --git a/.github/workflows/multios-unit-tests.yml b/.github/workflows/multios-unit-tests.yml index 8cefe9783a..b9f8a7f9ba 100644 --- a/.github/workflows/multios-unit-tests.yml +++ b/.github/workflows/multios-unit-tests.yml @@ -31,7 +31,7 @@ env: jobs: test-multi-os: - runs-on: "${{ (inputs.go-version == '1.20' && inputs.runs-on == 'windows-latest') && 'windows-2019' || inputs.runs-on }}" + runs-on: "${{ inputs.runs-on }}" env: REPORT: gotestsum-report.xml # path to where test results will be saved DD_APPSEC_WAF_TIMEOUT: 1h diff --git a/.github/workflows/parametric-tests.yml b/.github/workflows/parametric-tests.yml index eea919e7ce..5a91d0e3ce 100644 --- a/.github/workflows/parametric-tests.yml +++ b/.github/workflows/parametric-tests.yml @@ -43,7 +43,7 @@ jobs: - uses: actions/setup-go@v3 with: - go-version: "1.20" + go-version: "oldstable" - name: Build runner uses: ./.github/actions/install_runner diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index ad5016b050..128967216d 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -18,6 +18,6 @@ jobs: name: PR Unit and Integration Tests uses: ./.github/workflows/unit-integration-tests.yml with: - go-version: "1.20" + go-version: "1.21" ref: ${{ github.ref }} secrets: inherit diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index ea79e14067..9bc2c62905 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -76,7 +76,7 @@ jobs: ref: ${{ inputs.ref || github.ref }} - uses: actions/setup-go@v3 with: - go-version: "stable" + go-version: "1.21" cache: true - name: go mod tidy run: |- @@ -99,7 +99,7 @@ jobs: matrix: # TODO: cross-compilation from/to different hardware architectures once # github provides native ARM runners. - go: [ "1.20", "1.21", "1.22" ] + go: [ "1.21", "1.22", "1.23-rc" ] build-env: [ alpine, bookworm, bullseye ] build-with-cgo: [ 0, 1 ] deployment-env: [ alpine, debian11, debian12, al2, al2023, busybox, scratch ] diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index a5163c6115..27db6ebc7f 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -48,6 +48,7 @@ jobs: - APPSEC_BLOCKING_FULL_DENYLIST - APPSEC_REQUEST_BLOCKING - APPSEC_API_SECURITY + - APPSEC_RASP - APM_TRACING_E2E - APM_TRACING_E2E_SINGLE_SPAN - APM_TRACING_E2E_OTEL diff --git a/.github/workflows/unit-integration-tests.yml b/.github/workflows/unit-integration-tests.yml index e8db780839..81d027f00d 100644 --- a/.github/workflows/unit-integration-tests.yml +++ b/.github/workflows/unit-integration-tests.yml @@ -39,8 +39,9 @@ jobs: - name: golangci-lint uses: reviewdog/action-golangci-lint@v2 with: + golangci_lint_flags: "--timeout 10m" # We are hitting timeout when there is no cache go_version: ${{ inputs.go-version }} - golangci_lint_version: v1.52.2 + golangci_lint_version: v1.59.1 fail_on_error: true reporter: github-pr-review @@ -81,7 +82,7 @@ jobs: DD_POOL_TRACE_CHECK_FAILURES: true DD_DISABLE_ERROR_RESPONSES: true cassandra: - image: cassandra:3.7 + image: cassandra:3.11 env: JVM_OPTS: "-Xms750m -Xmx750m" ports: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fc9696ce0a..00f8b74e72 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,8 +4,8 @@ stages: - test-apps variables: - # This base image is created here: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/30723596 - BASE_CI_IMAGE: 486234852809.dkr.ecr.us-east-1.amazonaws.com/ci/benchmarking-platform:dd-trace-go-30723596 + # This base image is created here: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/38806487 + BASE_CI_IMAGE: 486234852809.dkr.ecr.us-east-1.amazonaws.com/ci/benchmarking-platform:dd-trace-go-38806487 INDEX_FILE: index.txt KUBERNETES_SERVICE_ACCOUNT_OVERWRITE: dd-trace-go FF_USE_LEGACY_KUBERNETES_EXECUTION_STRATEGY: "true" diff --git a/.gitlab/macrobenchmarks.yml b/.gitlab/macrobenchmarks.yml index 0b60a295d4..7e43d3719f 100644 --- a/.gitlab/macrobenchmarks.yml +++ b/.gitlab/macrobenchmarks.yml @@ -30,11 +30,14 @@ variables: GO_PROF_APP_BUILD_VARIANT: "candidate" DD_TRACE_GO_VERSION: "latest" + LOAD_TESTS: io-bound,cpu-bound,cgo-cpu-bound,cpu-bound-x-client-ip-enabled + PARALLELIZE: "true" # Workaround: Currently we're not running the benchmarks on every PR, but GitHub still shows them as pending. # By marking the benchmarks as allow_failure, this should go away. (This workaround should be removed once the # benchmarks get changed to run on every PR) allow_failure: true + go122-baseline: extends: .benchmarks variables: @@ -44,8 +47,7 @@ go122-baseline: ENABLE_APPSEC: "false" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.22.1" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" + go122-only-trace: extends: .benchmarks variables: @@ -55,8 +57,7 @@ go122-only-trace: ENABLE_APPSEC: "false" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.22.1" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" + go122-only-profile: extends: .benchmarks variables: @@ -66,8 +67,7 @@ go122-only-profile: ENABLE_APPSEC: "false" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.22.1" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" + go122-profile-trace: extends: .benchmarks variables: @@ -77,8 +77,7 @@ go122-profile-trace: ENABLE_APPSEC: "false" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.22.1" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" + go122-trace-asm: extends: .benchmarks variables: @@ -88,8 +87,7 @@ go122-trace-asm: ENABLE_APPSEC: "true" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.22.1" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" + go122-profile-trace-asm: extends: .benchmarks variables: @@ -99,8 +97,7 @@ go122-profile-trace-asm: ENABLE_APPSEC: "true" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.22.1" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" + go120-baseline: extends: .benchmarks variables: @@ -110,8 +107,7 @@ go120-baseline: ENABLE_APPSEC: "false" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.20.14" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" + go120-only-trace: extends: .benchmarks variables: @@ -121,8 +117,7 @@ go120-only-trace: ENABLE_APPSEC: "false" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.20.14" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" + go120-only-profile: extends: .benchmarks variables: @@ -132,8 +127,7 @@ go120-only-profile: ENABLE_APPSEC: "false" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.20.14" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" + go120-profile-trace: extends: .benchmarks variables: @@ -143,8 +137,7 @@ go120-profile-trace: ENABLE_APPSEC: "false" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.20.14" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" + go120-trace-asm: extends: .benchmarks variables: @@ -154,8 +147,7 @@ go120-trace-asm: ENABLE_APPSEC: "true" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.20.14" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" + go120-profile-trace-asm: extends: .benchmarks variables: @@ -165,5 +157,3 @@ go120-profile-trace-asm: ENABLE_APPSEC: "true" DD_PROFILING_EXECUTION_TRACE_ENABLED: "false" GO_VERSION: "1.20.14" - LOAD_TESTS: normal_operation_io-bound,high_load_io-bound|normal_operation_cpu-bound,high_load_cpu-bound|normal_operation_cgo-cpu-bound,high_load_cgo-cpu-bound|normal_operation_cpu-bound-x-client-ip-enabled,high_load_cpu-bound-x-client-ip-enabled - PARALLELIZE: "true" diff --git a/CODEOWNERS b/CODEOWNERS index f2a329aa03..6bba60c12b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -28,6 +28,9 @@ go.sum /datastreams @Datadog/data-streams-monitoring /internal/datastreams @Datadog/data-streams-monitoring +# civisibility +/internal/civisibility @DataDog/ci-app-libraries + # Gitlab configuration .gitlab-ci.yml @DataDog/dd-trace-go-guild @DataDog/apm-core-reliability-and-performance /.gitlab-ci @DataDog/dd-trace-go-guild @DataDog/apm-core-reliability-and-performance diff --git a/README.md b/README.md index 89094120c4..8ea1c5ad10 100644 --- a/README.md +++ b/README.md @@ -43,48 +43,9 @@ If you installed more packages than you intended, you can use `go mod tidy` to r - [Application Security Monitoring](https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/?code-lang=go) - If you are migrating from an older version of the tracer (e.g. 0.6.x) you may also find the [migration document](MIGRATING.md) we've put together helpful. -### Support Policy +### Go Support Policy -Datadog APM for Go is built upon dependencies defined in specific versions of the host operating system, Go releases, and the Datadog Agent/API. For Go the two latest releases are [GA](#support-ga) supported and the version before that is in [Maintenance](#support-maintenance). We do make efforts to support older releases, but generally these releases are considered [Legacy](#support-legacy). This library only officially supports [first class ports](https://github.com/golang/go/wiki/PortingPolicy#first-class-ports) of Go. - -| **Level** | **Support provided** | -|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| General Availability (GA) | Full implementation of all features. Full support for new features, bug & security fixes. | -| Maintenance | Full implementation of existing features. May receive new features. Support for bug & security fixes only. | -| Legacy | Legacy implementation. May have limited function, but no maintenance provided. Not guaranteed to compile the latest version of dd-trace-go. [Contact our customer support team for special requests.](https://www.datadoghq.com/support/) | - -### Supported Versions - -| **Go Version** | **Support level** | -|----------------|-------------------------------------| -| 1.22 | [GA](#support-ga) | -| 1.21 | [GA](#support-ga) | -| 1.20 | [Maintenance](#support-maintenance) | -| 1.19 | [Legacy](#support-legacy) | - -* Datadog's Trace Agent >= 5.21.1 - - -#### Package Versioning - -A **Minor** version change will be released whenever a new version of Go is released. At that time the newest version of Go is added to [GA](#support-ga), the second oldest supported version moved to [Maintenance](#support-maintenance) and the oldest previously supported version dropped to [Legacy](#support-legacy). -**For example**: -For a dd-trace-go version 1.37.* - -| Go Version | Support | -|------------|-------------------------------------| -| 1.18 | [GA](#support-ga) | -| 1.17 | [GA](#support-ga) | -| 1.16 | [Maintenance](#support-maintenance) | - -Then after Go 1.19 is released there will be a new dd-trace-go version 1.38.0 with support: - -| Go Version | Support | -|------------|-------------------------------------| -| 1.19 | [GA](#support-ga) | -| 1.18 | [GA](#support-ga) | -| 1.17 | [Maintenance](#support-maintenance) | -| 1.16 | [Legacy](#support-legacy) | +Datadog APM for Go is built upon dependencies defined in specific versions of the host operating system, Go releases, and the Datadog Agent/API. dd-trace-go supports the two latest releases of Go, matching the [official Go policy](https://go.dev/doc/devel/release#policy). This library only officially supports [first class ports](https://go.dev/wiki/PortingPolicy) of Go. ### Contributing @@ -105,4 +66,4 @@ If you're only interested in the tests for a specific integration it can be usef For example if you're running tests that need the `mysql` database container to be up: ```shell docker compose -f docker-compose.yaml -p dd-trace-go up -d mysql -``` \ No newline at end of file +``` diff --git a/appsec/events/block.go b/appsec/events/block.go index 8db51b8268..b405bdd97d 100644 --- a/appsec/events/block.go +++ b/appsec/events/block.go @@ -11,8 +11,6 @@ import "errors" var _ error = (*BlockingSecurityEvent)(nil) -var securityError = &BlockingSecurityEvent{} - // BlockingSecurityEvent is the error type returned by function calls blocked by appsec. // Even though appsec takes care of responding automatically to the blocked requests, it // is your duty to abort the request handlers that are calling functions blocked by appsec. @@ -29,5 +27,6 @@ func (*BlockingSecurityEvent) Error() string { // IsSecurityError returns true if the error is a security event. func IsSecurityError(err error) bool { - return errors.Is(err, securityError) + var secErr *BlockingSecurityEvent + return errors.As(err, &secErr) } diff --git a/contrib/99designs/gqlgen/option.go b/contrib/99designs/gqlgen/option.go index d098dda82f..194ba3f4a2 100644 --- a/contrib/99designs/gqlgen/option.go +++ b/contrib/99designs/gqlgen/option.go @@ -15,9 +15,11 @@ import ( const defaultServiceName = "graphql" type config struct { - serviceName string - analyticsRate float64 - tags map[string]interface{} + serviceName string + analyticsRate float64 + withoutTraceIntrospectionQuery bool + withoutTraceTrivialResolvedFields bool + tags map[string]interface{} } // An Option configures the gqlgen integration. @@ -51,6 +53,21 @@ func WithServiceName(name string) Option { } } +// WithoutTraceIntrospectionQuery skips creating spans for fields when the operation name is IntrospectionQuery. +func WithoutTraceIntrospectionQuery() Option { + return func(cfg *config) { + cfg.withoutTraceIntrospectionQuery = true + } +} + +// WithoutTraceTrivialResolvedFields skips creating spans for fields that have a trivial resolver. +// For example, a field resolved from an object w/o requiring a custom method is considered trivial. +func WithoutTraceTrivialResolvedFields() Option { + return func(cfg *config) { + cfg.withoutTraceTrivialResolvedFields = true + } +} + // WithCustomTag will attach the value to the span tagged by the key. func WithCustomTag(key string, value interface{}) Option { return func(cfg *config) { diff --git a/contrib/99designs/gqlgen/tracer.go b/contrib/99designs/gqlgen/tracer.go index 9a7954959b..d6341d2a96 100644 --- a/contrib/99designs/gqlgen/tracer.go +++ b/contrib/99designs/gqlgen/tracer.go @@ -104,12 +104,12 @@ func (t *gqlTracer) Validate(_ graphql.ExecutableSchema) error { func (t *gqlTracer) InterceptOperation(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler { opCtx := graphql.GetOperationContext(ctx) span, ctx := t.createRootSpan(ctx, opCtx) - ctx, req := graphqlsec.StartRequestOperation(ctx, nil /* root */, span, types.RequestOperationArgs{ + ctx, req := graphqlsec.StartRequestOperation(ctx, span, types.RequestOperationArgs{ RawQuery: opCtx.RawQuery, OperationName: opCtx.OperationName, Variables: opCtx.Variables, }) - ctx, query := graphqlsec.StartExecutionOperation(ctx, req, span, types.ExecutionOperationArgs{ + ctx, query := graphqlsec.StartExecutionOperation(ctx, span, types.ExecutionOperationArgs{ Query: opCtx.RawQuery, OperationName: opCtx.OperationName, Variables: opCtx.Variables, @@ -138,7 +138,18 @@ func (t *gqlTracer) InterceptOperation(ctx context.Context, next graphql.Operati func (t *gqlTracer) InterceptField(ctx context.Context, next graphql.Resolver) (res any, err error) { opCtx := graphql.GetOperationContext(ctx) + if t.cfg.withoutTraceIntrospectionQuery && opCtx.OperationName == "IntrospectionQuery" { + res, err = next(ctx) + return + } + fieldCtx := graphql.GetFieldContext(ctx) + isTrivial := !(fieldCtx.IsMethod || fieldCtx.IsResolver) + if t.cfg.withoutTraceTrivialResolvedFields && isTrivial { + res, err = next(ctx) + return + } + opts := make([]tracer.StartSpanOption, 0, 6+len(t.cfg.tags)) for k, v := range t.cfg.tags { opts = append(opts, tracer.Tag(k, v)) @@ -153,15 +164,17 @@ func (t *gqlTracer) InterceptField(ctx context.Context, next graphql.Resolver) ( if !math.IsNaN(t.cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, t.cfg.analyticsRate)) } + span, ctx := tracer.StartSpanFromContext(ctx, fieldOp, opts...) defer func() { span.Finish(tracer.WithError(err)) }() - ctx, op := graphqlsec.StartResolveOperation(ctx, graphqlsec.FromContext[*types.ExecutionOperation](ctx), span, types.ResolveOperationArgs{ + ctx, op := graphqlsec.StartResolveOperation(ctx, span, types.ResolveOperationArgs{ Arguments: fieldCtx.Args, TypeName: fieldCtx.Object, FieldName: fieldCtx.Field.Name, - Trivial: !(fieldCtx.IsMethod || fieldCtx.IsResolver), // TODO: Is this accurate? + Trivial: isTrivial, }) defer func() { op.Finish(types.ResolveOperationRes{Data: res, Error: err}) }() + res, err = next(ctx) return } diff --git a/contrib/99designs/gqlgen/tracer_test.go b/contrib/99designs/gqlgen/tracer_test.go index 1fa928e2bc..0517aef976 100644 --- a/contrib/99designs/gqlgen/tracer_test.go +++ b/contrib/99designs/gqlgen/tracer_test.go @@ -29,10 +29,10 @@ func TestOptions(t *testing.T) { query := `{ name }` for name, tt := range map[string]struct { tracerOpts []Option - test func(assert *assert.Assertions, root mocktracer.Span) + test func(assert *assert.Assertions, root mocktracer.Span, spans []mocktracer.Span) }{ "default": { - test: func(assert *assert.Assertions, root mocktracer.Span) { + test: func(assert *assert.Assertions, root mocktracer.Span, _ []mocktracer.Span) { assert.Equal("graphql.query", root.OperationName()) assert.Equal(query, root.Tag(ext.ResourceName)) assert.Equal(defaultServiceName, root.Tag(ext.ServiceName)) @@ -43,34 +43,47 @@ func TestOptions(t *testing.T) { }, "WithServiceName": { tracerOpts: []Option{WithServiceName("TestServer")}, - test: func(assert *assert.Assertions, root mocktracer.Span) { + test: func(assert *assert.Assertions, root mocktracer.Span, _ []mocktracer.Span) { assert.Equal("TestServer", root.Tag(ext.ServiceName)) }, }, "WithAnalytics/true": { tracerOpts: []Option{WithAnalytics(true)}, - test: func(assert *assert.Assertions, root mocktracer.Span) { + test: func(assert *assert.Assertions, root mocktracer.Span, _ []mocktracer.Span) { assert.Equal(1.0, root.Tag(ext.EventSampleRate)) }, }, "WithAnalytics/false": { tracerOpts: []Option{WithAnalytics(false)}, - test: func(assert *assert.Assertions, root mocktracer.Span) { + test: func(assert *assert.Assertions, root mocktracer.Span, _ []mocktracer.Span) { assert.Nil(root.Tag(ext.EventSampleRate)) }, }, "WithAnalyticsRate": { tracerOpts: []Option{WithAnalyticsRate(0.5)}, - test: func(assert *assert.Assertions, root mocktracer.Span) { + test: func(assert *assert.Assertions, root mocktracer.Span, _ []mocktracer.Span) { assert.Equal(0.5, root.Tag(ext.EventSampleRate)) }, }, + "WithoutTraceTrivialResolvedFields": { + tracerOpts: []Option{WithoutTraceTrivialResolvedFields()}, + test: func(assert *assert.Assertions, _ mocktracer.Span, spans []mocktracer.Span) { + var hasFieldOperation bool + for _, span := range spans { + if span.OperationName() == fieldOp { + hasFieldOperation = true + break + } + } + assert.Equal(false, hasFieldOperation) + }, + }, "WithCustomTag": { tracerOpts: []Option{ WithCustomTag("customTag1", "customValue1"), WithCustomTag("customTag2", "customValue2"), }, - test: func(assert *assert.Assertions, root mocktracer.Span) { + test: func(assert *assert.Assertions, root mocktracer.Span, _ []mocktracer.Span) { assert.Equal("customValue1", root.Tag("customTag1")) assert.Equal("customValue2", root.Tag("customTag2")) }, @@ -82,17 +95,48 @@ func TestOptions(t *testing.T) { defer mt.Stop() c := newTestClient(t, testserver.New(), NewTracer(tt.tracerOpts...)) c.MustPost(query, &testServerResponse{}) + spans := mt.FinishedSpans() var root mocktracer.Span - for _, span := range mt.FinishedSpans() { + for _, span := range spans { if span.ParentID() == 0 { root = span } } assert.NotNil(root) - tt.test(assert, root) + tt.test(assert, root, spans) assert.Nil(root.Tag(ext.Error)) }) } + + // WithoutTraceIntrospectionQuery tested here since we are specifically checking against an IntrosepctionQuery operation. + query = `query IntrospectionQuery { __schema { queryType { name } } }` + for name, tt := range map[string]struct { + tracerOpts []Option + test func(assert *assert.Assertions, spans []mocktracer.Span) + }{ + "WithoutTraceIntrospectionQuery": { + tracerOpts: []Option{WithoutTraceIntrospectionQuery()}, + test: func(assert *assert.Assertions, spans []mocktracer.Span) { + var hasFieldSpan bool + for _, span := range spans { + if span.OperationName() == fieldOp { + hasFieldSpan = true + break + } + } + assert.Equal(false, hasFieldSpan) + }, + }, + } { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + c := newTestClient(t, testserver.New(), NewTracer(tt.tracerOpts...)) + c.MustPost(query, &testServerResponse{}, client.Operation("IntrospectionQuery")) + tt.test(assert, mt.FinishedSpans()) + }) + } } func TestError(t *testing.T) { @@ -172,7 +216,7 @@ func TestNamingSchema(t *testing.T) { err := c.Post(`{ name }`, &testServerResponse{}) require.NoError(t, err) - err = c.Post(`mutation Name() { name }`, &testServerResponse{}) + err = c.Post(`mutation Name { name }`, &testServerResponse{}) assert.ErrorContains(t, err, "mutations are not supported") return mt.FinishedSpans() diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 5b7f6579de..91cadea451 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -11,10 +11,13 @@ import ( "math" "time" + "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/options" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/sqlsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) @@ -54,6 +57,15 @@ type TracedConn struct { *traceParams } +// checkQuerySafety runs ASM RASP SQLi checks on the query to verify if it can safely be run. +// If it's unsafe to run, an *events.BlockingSecurityEvent is returned +func checkQuerySecurity(ctx context.Context, query, driver string) error { + if !appsec.Enabled() { + return nil + } + return sqlsec.ProtectSQLOperation(ctx, query, driver) +} + // WrappedConn returns the wrapped connection object. func (tc *TracedConn) WrappedConn() driver.Conn { return tc.Conn @@ -135,7 +147,9 @@ func (tc *TracedConn) ExecContext(ctx context.Context, query string, args []driv cquery, spanID := tc.injectComments(ctx, query, tc.cfg.dbmPropagationMode) ctx, end := startTraceTask(ctx, QueryTypeExec) defer end() - r, err := execContext.ExecContext(ctx, cquery, args) + if err = checkQuerySecurity(ctx, query, tc.driverName); !events.IsSecurityError(err) { + r, err = execContext.ExecContext(ctx, cquery, args) + } tc.tryTrace(ctx, QueryTypeExec, query, start, err, append(withDBMTraceInjectedTag(tc.cfg.dbmPropagationMode), tracer.WithSpanID(spanID))...) return r, err } @@ -152,7 +166,9 @@ func (tc *TracedConn) ExecContext(ctx context.Context, query string, args []driv cquery, spanID := tc.injectComments(ctx, query, tc.cfg.dbmPropagationMode) ctx, end := startTraceTask(ctx, QueryTypeExec) defer end() - r, err = execer.Exec(cquery, dargs) + if err = checkQuerySecurity(ctx, query, tc.driverName); !events.IsSecurityError(err) { + r, err = execer.Exec(cquery, dargs) + } tc.tryTrace(ctx, QueryTypeExec, query, start, err, append(withDBMTraceInjectedTag(tc.cfg.dbmPropagationMode), tracer.WithSpanID(spanID))...) return r, err } @@ -179,7 +195,9 @@ func (tc *TracedConn) QueryContext(ctx context.Context, query string, args []dri cquery, spanID := tc.injectComments(ctx, query, tc.cfg.dbmPropagationMode) ctx, end := startTraceTask(ctx, QueryTypeQuery) defer end() - rows, err := queryerContext.QueryContext(ctx, cquery, args) + if err = checkQuerySecurity(ctx, query, tc.driverName); !events.IsSecurityError(err) { + rows, err = queryerContext.QueryContext(ctx, cquery, args) + } tc.tryTrace(ctx, QueryTypeQuery, query, start, err, append(withDBMTraceInjectedTag(tc.cfg.dbmPropagationMode), tracer.WithSpanID(spanID))...) return rows, err } @@ -196,7 +214,9 @@ func (tc *TracedConn) QueryContext(ctx context.Context, query string, args []dri cquery, spanID := tc.injectComments(ctx, query, tc.cfg.dbmPropagationMode) ctx, end := startTraceTask(ctx, QueryTypeQuery) defer end() - rows, err = queryer.Query(cquery, dargs) + if err = checkQuerySecurity(ctx, query, tc.driverName); !events.IsSecurityError(err) { + rows, err = queryer.Query(cquery, dargs) + } tc.tryTrace(ctx, QueryTypeQuery, query, start, err, append(withDBMTraceInjectedTag(tc.cfg.dbmPropagationMode), tracer.WithSpanID(spanID))...) return rows, err } @@ -332,6 +352,7 @@ func (tp *traceParams) tryTrace(ctx context.Context, qtype QueryType, query stri if query != "" { resource = query } + span.SetTag("sql.query_type", string(qtype)) span.SetTag(ext.ResourceName, resource) for k, v := range tp.meta { @@ -342,7 +363,7 @@ func (tp *traceParams) tryTrace(ctx context.Context, qtype QueryType, query stri span.SetTag(k, v) } } - if err != nil && (tp.cfg.errCheck == nil || tp.cfg.errCheck(err)) { + if err != nil && !events.IsSecurityError(err) && (tp.cfg.errCheck == nil || tp.cfg.errCheck(err)) { span.SetTag(ext.Error, err) } span.Finish() diff --git a/contrib/gocql/gocql/gocql.go b/contrib/gocql/gocql/gocql.go index 7881392ba4..03a84c6002 100644 --- a/contrib/gocql/gocql/gocql.go +++ b/contrib/gocql/gocql/gocql.go @@ -205,6 +205,14 @@ func (tq *Query) MapScan(m map[string]interface{}) error { return err } +// MapScanCAS wraps in a span query.MapScanCAS call. +func (tq *Query) MapScanCAS(m map[string]interface{}) (applied bool, err error) { + span := tq.newChildSpan(tq.ctx) + applied, err = tq.Query.MapScanCAS(m) + tq.finishSpan(span, err) + return applied, err +} + // Scan wraps in a span query.Scan call. func (tq *Query) Scan(dest ...interface{}) error { span := tq.newChildSpan(tq.ctx) @@ -242,7 +250,15 @@ func (tq *Query) Iter() *Iter { if tIter.Host() != nil { tIter.span.SetTag(ext.TargetHost, tIter.Iter.Host().HostID()) tIter.span.SetTag(ext.TargetPort, strconv.Itoa(tIter.Iter.Host().Port())) - tIter.span.SetTag(ext.CassandraCluster, tIter.Iter.Host().DataCenter()) + + cluster := tIter.Iter.Host().ClusterName() + dc := tIter.Iter.Host().DataCenter() + if tq.config.clusterTagLegacyMode { + tIter.span.SetTag(ext.CassandraCluster, dc) + } else { + tIter.span.SetTag(ext.CassandraCluster, cluster) + } + tIter.span.SetTag(ext.CassandraDatacenter, dc) } return tIter } diff --git a/contrib/gocql/gocql/gocql_test.go b/contrib/gocql/gocql/gocql_test.go index 73a6ba3275..1ff7ea21ee 100644 --- a/contrib/gocql/gocql/gocql_test.go +++ b/contrib/gocql/gocql/gocql_test.go @@ -105,7 +105,8 @@ func TestErrorWrapper(t *testing.T) { if iter.Host() != nil { assert.Equal(span.Tag(ext.TargetPort), "9042") assert.Equal(span.Tag(ext.TargetHost), iter.Host().HostID()) - assert.Equal(span.Tag(ext.CassandraCluster), "datacenter1") + assert.Equal(span.Tag(ext.CassandraCluster), "Test Cluster") + assert.Equal(span.Tag(ext.CassandraDatacenter), "datacenter1") } } @@ -152,7 +153,71 @@ func TestChildWrapperSpan(t *testing.T) { if iter.Host() != nil { assert.Equal(childSpan.Tag(ext.TargetPort), "9042") assert.Equal(childSpan.Tag(ext.TargetHost), iter.Host().HostID()) - assert.Equal(childSpan.Tag(ext.CassandraCluster), "datacenter1") + assert.Equal(childSpan.Tag(ext.CassandraCluster), "Test Cluster") + assert.Equal(childSpan.Tag(ext.CassandraDatacenter), "datacenter1") + } +} + +func TestCompatMode(t *testing.T) { + genSpans := func(t *testing.T) []mocktracer.Span { + mt := mocktracer.Start() + defer mt.Stop() + + cluster := newCassandraCluster() + session, err := cluster.CreateSession() + require.NoError(t, err) + + q := session.Query("SELECT * FROM trace.person").WithContext(context.Background()) + tq := WrapQuery(q, WithServiceName("TestServiceName")) + iter := tq.Iter() + err = iter.Close() + require.NoError(t, err) + + spans := mt.FinishedSpans() + require.Len(t, spans, 1) + return spans + } + testCases := []struct { + name string + gocqlCompat string + wantCluster string + }{ + { + name: "== v1.65", + gocqlCompat: "v1.65", + wantCluster: "datacenter1", + }, + { + name: "< v1.65", + gocqlCompat: "v1.64", + wantCluster: "datacenter1", + }, + { + name: "> v1.65", + gocqlCompat: "v1.66", + wantCluster: "Test Cluster", + }, + { + name: "empty", + gocqlCompat: "", + wantCluster: "Test Cluster", + }, + { + name: "bad version", + gocqlCompat: "bad-version", + wantCluster: "Test Cluster", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Setenv("DD_TRACE_GOCQL_COMPAT", tc.gocqlCompat) + spans := genSpans(t) + s := spans[0] + assert.Equal(t, s.Tag(ext.TargetPort), "9042") + assert.NotEmpty(t, s.Tag(ext.TargetHost)) + assert.Equal(t, tc.wantCluster, s.Tag(ext.CassandraCluster)) + assert.Equal(t, "datacenter1", s.Tag(ext.CassandraDatacenter)) + }) } } diff --git a/contrib/gocql/gocql/option.go b/contrib/gocql/gocql/option.go index baee715fb4..c0df25d2b2 100644 --- a/contrib/gocql/gocql/option.go +++ b/contrib/gocql/gocql/option.go @@ -7,8 +7,12 @@ package gocql import ( "math" + "os" + + "golang.org/x/mod/semver" "gopkg.in/DataDog/dd-trace-go.v1/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema" ) @@ -21,6 +25,7 @@ type queryConfig struct { analyticsRate float64 errCheck func(err error) bool customTags map[string]interface{} + clusterTagLegacyMode bool } // WrapOption represents an option that can be passed to WrapQuery. @@ -37,6 +42,13 @@ func defaultConfig() *queryConfig { } else { cfg.analyticsRate = math.NaN() } + if compatMode := os.Getenv("DD_TRACE_GOCQL_COMPAT"); compatMode != "" { + if semver.IsValid(compatMode) { + cfg.clusterTagLegacyMode = semver.Compare(semver.MajorMinor(compatMode), "v1.65") <= 0 + } else { + log.Warn("ignoring DD_TRACE_GOCQL_COMPAT: invalid version %q", compatMode) + } + } cfg.errCheck = func(error) bool { return true } return cfg } diff --git a/contrib/gorm.io/gorm.v1/example_test.go b/contrib/gorm.io/gorm.v1/example_test.go index ec45b76a00..2b75405503 100644 --- a/contrib/gorm.io/gorm.v1/example_test.go +++ b/contrib/gorm.io/gorm.v1/example_test.go @@ -7,6 +7,7 @@ package gorm_test import ( "context" + "errors" "log" sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql" @@ -41,6 +42,31 @@ func ExampleOpen() { db.Where("name = ?", "jinzhu").First(&user) } +// ExampleNewTracePlugin illustrates how to trace gorm using the gorm.Plugin api. +func ExampleNewTracePlugin() { + // Register augments the provided driver with tracing, enabling it to be loaded by gorm.Open and the gormtrace.TracePlugin. + sqltrace.Register("pgx", &stdlib.Driver{}, sqltrace.WithServiceName("my-service")) + sqlDb, err := sqltrace.Open("pgx", "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable") + if err != nil { + log.Fatal(err) + } + db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDb}), &gorm.Config{}) + if err != nil { + log.Fatal(err) + } + var user User + + errCheck := gormtrace.WithErrorCheck(func(err error) bool { + return !errors.Is(err, gorm.ErrRecordNotFound) + }) + if err := db.Use(gormtrace.NewTracePlugin(errCheck)); err != nil { + log.Fatal(err) + } + + // All calls through gorm.DB are now traced. + db.Where("name = ?", "jinzhu").First(&user) +} + func ExampleContext() { // Register augments the provided driver with tracing, enabling it to be loaded by gormtrace.Open. sqltrace.Register("pgx", &stdlib.Driver{}, sqltrace.WithServiceName("my-service")) diff --git a/contrib/gorm.io/gorm.v1/gorm.go b/contrib/gorm.io/gorm.v1/gorm.go index 0444065114..b87b8b79a0 100644 --- a/contrib/gorm.io/gorm.v1/gorm.go +++ b/contrib/gorm.io/gorm.v1/gorm.go @@ -7,9 +7,7 @@ package gorm import ( - "context" "math" - "time" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" @@ -33,6 +31,26 @@ const ( gormSpanStartTimeKey = key("dd-trace-go:span") ) +type tracePlugin struct { + options []Option +} + +// NewTracePlugin returns a new gorm.Plugin that enhances the underlying *gorm.DB with tracing. +func NewTracePlugin(opts ...Option) gorm.Plugin { + return tracePlugin{ + options: opts, + } +} + +func (tracePlugin) Name() string { + return "DDTracePlugin" +} + +func (g tracePlugin) Initialize(db *gorm.DB) error { + _, err := withCallbacks(db, g.options...) + return err +} + // Open opens a new (traced) database connection. The used driver must be formerly registered // using (gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql).Register. func Open(dialector gorm.Dialector, cfg *gorm.Config, opts ...Option) (*gorm.DB, error) { @@ -57,86 +75,80 @@ func withCallbacks(db *gorm.DB, opts ...Option) (*gorm.DB, error) { } log.Debug("Registering Callbacks: %#v", cfg) - afterFunc := func(operationName string) func(*gorm.DB) { + afterFunc := func() func(*gorm.DB) { + return func(db *gorm.DB) { + after(db, cfg) + } + } + + beforeFunc := func(operationName string) func(*gorm.DB) { return func(db *gorm.DB) { - after(db, operationName, cfg) + before(db, operationName, cfg) } } cb := db.Callback() - err := cb.Create().Before("gorm:create").Register("dd-trace-go:before_create", before) + err := cb.Create().Before("gorm:create").Register("dd-trace-go:before_create", beforeFunc("gorm.create")) if err != nil { return db, err } - err = cb.Create().After("gorm:create").Register("dd-trace-go:after_create", afterFunc("gorm.create")) + err = cb.Create().After("gorm:create").Register("dd-trace-go:after_create", afterFunc()) if err != nil { return db, err } - err = cb.Update().Before("gorm:update").Register("dd-trace-go:before_update", before) + err = cb.Update().Before("gorm:update").Register("dd-trace-go:before_update", beforeFunc("gorm.update")) if err != nil { return db, err } - err = cb.Update().After("gorm:update").Register("dd-trace-go:after_update", afterFunc("gorm.update")) + err = cb.Update().After("gorm:update").Register("dd-trace-go:after_update", afterFunc()) if err != nil { return db, err } - err = cb.Delete().Before("gorm:delete").Register("dd-trace-go:before_delete", before) + err = cb.Delete().Before("gorm:delete").Register("dd-trace-go:before_delete", beforeFunc("gorm.delete")) if err != nil { return db, err } - err = cb.Delete().After("gorm:delete").Register("dd-trace-go:after_delete", afterFunc("gorm.delete")) + err = cb.Delete().After("gorm:delete").Register("dd-trace-go:after_delete", afterFunc()) if err != nil { return db, err } - err = cb.Query().Before("gorm:query").Register("dd-trace-go:before_query", before) + err = cb.Query().Before("gorm:query").Register("dd-trace-go:before_query", beforeFunc("gorm.query")) if err != nil { return db, err } - err = cb.Query().After("gorm:query").Register("dd-trace-go:after_query", afterFunc("gorm.query")) + err = cb.Query().After("gorm:query").Register("dd-trace-go:after_query", afterFunc()) if err != nil { return db, err } - err = cb.Row().Before("gorm:query").Register("dd-trace-go:before_row_query", before) + err = cb.Row().Before("gorm:row").Register("dd-trace-go:before_row_query", beforeFunc("gorm.row_query")) if err != nil { return db, err } - err = cb.Row().After("gorm:query").Register("dd-trace-go:after_row_query", afterFunc("gorm.row_query")) + err = cb.Row().After("gorm:row").Register("dd-trace-go:after_row_query", afterFunc()) if err != nil { return db, err } - err = cb.Raw().Before("gorm:query").Register("dd-trace-go:before_raw_query", before) + err = cb.Raw().Before("gorm:raw").Register("dd-trace-go:before_raw_query", beforeFunc("gorm.raw_query")) if err != nil { return db, err } - err = cb.Raw().After("gorm:query").Register("dd-trace-go:after_raw_query", afterFunc("gorm.raw_query")) + err = cb.Raw().After("gorm:raw").Register("dd-trace-go:after_raw_query", afterFunc()) if err != nil { return db, err } return db, nil } -func before(scope *gorm.DB) { - if scope.Statement != nil && scope.Statement.Context != nil { - scope.Statement.Context = context.WithValue(scope.Statement.Context, gormSpanStartTimeKey, time.Now()) - } -} - -func after(db *gorm.DB, operationName string, cfg *config) { +func before(db *gorm.DB, operationName string, cfg *config) { if db.Statement == nil || db.Statement.Context == nil { return } - - ctx := db.Statement.Context - t, ok := ctx.Value(gormSpanStartTimeKey).(time.Time) - if !ok { + if db.Config == nil || db.Config.DryRun { return } - opts := []ddtrace.StartSpanOption{ - tracer.StartTime(t), tracer.ServiceName(cfg.serviceName), tracer.SpanType(ext.SpanTypeSQL), - tracer.ResourceName(db.Statement.SQL.String()), tracer.Tag(ext.Component, componentName), } if !math.IsNaN(cfg.analyticsRate) { @@ -148,10 +160,24 @@ func after(db *gorm.DB, operationName string, cfg *config) { } } - span, _ := tracer.StartSpanFromContext(ctx, operationName, opts...) - var dbErr error - if cfg.errCheck(db.Error) { - dbErr = db.Error + _, ctx := tracer.StartSpanFromContext(db.Statement.Context, operationName, opts...) + db.Statement.Context = ctx +} + +func after(db *gorm.DB, cfg *config) { + if db.Statement == nil || db.Statement.Context == nil { + return + } + if db.Config == nil || db.Config.DryRun { + return + } + span, ok := tracer.SpanFromContext(db.Statement.Context) + if ok { + var dbErr error + if cfg.errCheck(db.Error) { + dbErr = db.Error + } + span.SetTag(ext.ResourceName, db.Statement.SQL.String()) + span.Finish(tracer.WithError(dbErr)) } - span.Finish(tracer.WithError(dbErr)) } diff --git a/contrib/gorm.io/gorm.v1/gorm_test.go b/contrib/gorm.io/gorm.v1/gorm_test.go index cb1a11f95b..8dac64bfcf 100644 --- a/contrib/gorm.io/gorm.v1/gorm_test.go +++ b/contrib/gorm.io/gorm.v1/gorm_test.go @@ -24,10 +24,12 @@ import ( _ "github.com/lib/pq" mssql "github.com/microsoft/go-mssqldb" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" mysqlgorm "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlserver" "gorm.io/gorm" + "gorm.io/gorm/utils/tests" ) // tableName holds the SQL table that these tests will be run against. It must be unique cross-repo. @@ -172,10 +174,6 @@ type Product struct { } func TestCallbacks(t *testing.T) { - a := assert.New(t) - mt := mocktracer.Start() - defer mt.Stop() - sqltrace.Register("pgx", &stdlib.Driver{}) sqlDb, err := sqltrace.Open("pgx", pgConnString) if err != nil { @@ -193,12 +191,15 @@ func TestCallbacks(t *testing.T) { } t.Run("create", func(t *testing.T) { + a := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "http.request", tracer.ServiceName("fake-http-server"), tracer.SpanType(ext.SpanTypeWeb), ) - db = db.WithContext(ctx) + db := db.WithContext(ctx) var queryText string db.Callback().Create().After("testing").Register("query text", func(d *gorm.DB) { queryText = d.Statement.SQL.String() @@ -215,15 +216,26 @@ func TestCallbacks(t *testing.T) { a.Equal(ext.SpanTypeSQL, span.Tag(ext.SpanType)) a.Equal(queryText, span.Tag(ext.ResourceName)) a.Equal("gorm.io/gorm.v1", span.Tag(ext.Component)) + a.Equal(parentSpan.Context().SpanID(), span.ParentID()) + + for _, s := range spans { + if s.Tag(ext.Component) == "jackc/pgx.v5" { + // The underlying driver should receive the gorm span + a.Equal(span.SpanID(), s.ParentID()) + } + } }) t.Run("query", func(t *testing.T) { + a := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "http.request", tracer.ServiceName("fake-http-server"), tracer.SpanType(ext.SpanTypeWeb), ) - db = db.WithContext(ctx) + db := db.WithContext(ctx) var queryText string db.Callback().Query().After("testing").Register("query text", func(d *gorm.DB) { queryText = d.Statement.SQL.String() @@ -241,15 +253,46 @@ func TestCallbacks(t *testing.T) { a.Equal(ext.SpanTypeSQL, span.Tag(ext.SpanType)) a.Equal(queryText, span.Tag(ext.ResourceName)) a.Equal("gorm.io/gorm.v1", span.Tag(ext.Component)) + a.Equal(parentSpan.Context().SpanID(), span.ParentID()) + + for _, s := range spans { + if s.Tag(ext.Component) == "jackc/pgx.v5" { + // The underlying driver should receive the gorm span + a.Equal(span.SpanID(), s.ParentID()) + } + } + }) + + t.Run("dry_run", func(t *testing.T) { + a := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "http.request", + tracer.ServiceName("fake-http-server"), + tracer.SpanType(ext.SpanTypeWeb), + ) + + db := db.WithContext(ctx) + db.DryRun = true + var product Product + db.First(&product, "code = ?", "L1212") + + parentSpan.Finish() + + spans := mt.FinishedSpans() + a.Len(spans, 1) // No additional span generated }) t.Run("update", func(t *testing.T) { + a := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "http.request", tracer.ServiceName("fake-http-server"), tracer.SpanType(ext.SpanTypeWeb), ) - db = db.WithContext(ctx) + db := db.WithContext(ctx) var queryText string db.Callback().Update().After("testing").Register("query text", func(d *gorm.DB) { queryText = d.Statement.SQL.String() @@ -268,15 +311,26 @@ func TestCallbacks(t *testing.T) { a.Equal(ext.SpanTypeSQL, span.Tag(ext.SpanType)) a.Equal(queryText, span.Tag(ext.ResourceName)) a.Equal("gorm.io/gorm.v1", span.Tag(ext.Component)) + a.Equal(parentSpan.Context().SpanID(), span.ParentID()) + + for _, s := range spans { + if s.Tag(ext.Component) == "jackc/pgx.v5" { + // The underlying driver should receive the gorm span + a.Equal(span.SpanID(), s.ParentID()) + } + } }) t.Run("delete", func(t *testing.T) { + a := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "http.request", tracer.ServiceName("fake-http-server"), tracer.SpanType(ext.SpanTypeWeb), ) - db = db.WithContext(ctx) + db := db.WithContext(ctx) var queryText string db.Callback().Delete().After("testing").Register("query text", func(d *gorm.DB) { queryText = d.Statement.SQL.String() @@ -295,15 +349,26 @@ func TestCallbacks(t *testing.T) { a.Equal(ext.SpanTypeSQL, span.Tag(ext.SpanType)) a.Equal(queryText, span.Tag(ext.ResourceName)) a.Equal("gorm.io/gorm.v1", span.Tag(ext.Component)) + a.Equal(parentSpan.Context().SpanID(), span.ParentID()) + + for _, s := range spans { + if s.Tag(ext.Component) == "jackc/pgx.v5" { + // The underlying driver should receive the gorm span + a.Equal(span.SpanID(), s.ParentID()) + } + } }) t.Run("raw", func(t *testing.T) { + a := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "http.request", tracer.ServiceName("fake-http-server"), tracer.SpanType(ext.SpanTypeWeb), ) - db = db.WithContext(ctx) + db := db.WithContext(ctx) var queryText string db.Callback().Raw().After("testing").Register("query text", func(d *gorm.DB) { queryText = d.Statement.SQL.String() @@ -321,6 +386,13 @@ func TestCallbacks(t *testing.T) { a.Equal("gorm.raw_query", span.OperationName()) a.Equal(ext.SpanTypeSQL, span.Tag(ext.SpanType)) a.Equal(queryText, span.Tag(ext.ResourceName)) + + for _, s := range spans { + if s.Tag(ext.Component) == "jackc/pgx.v5" { + // The underlying driver should receive the gorm span + a.Equal(span.SpanID(), s.ParentID()) + } + } }) } @@ -487,7 +559,7 @@ func TestCustomTags(t *testing.T) { db, err := Open( postgres.New(postgres.Config{Conn: sqlDb}), &gorm.Config{}, - WithCustomTag("foo", func(db *gorm.DB) interface{} { + WithCustomTag("foo", func(_ *gorm.DB) interface{} { return "bar" }), ) @@ -511,3 +583,35 @@ func TestCustomTags(t *testing.T) { assert.Equal("bar", s.Tag("foo")) } + +func TestPlugin(t *testing.T) { + db, err := gorm.Open(&tests.DummyDialector{}) + require.NoError(t, err) + + opt := WithCustomTag("foo", func(_ *gorm.DB) interface{} { + return "bar" + }) + plugin := NewTracePlugin(opt).(tracePlugin) + + assert.Equal(t, "DDTracePlugin", plugin.Name()) + assert.Len(t, plugin.options, 1) + require.NoError(t, db.Use(plugin)) + + assert.NotNil(t, db.Callback().Create().Get("dd-trace-go:before_create")) + assert.NotNil(t, db.Callback().Create().Get("dd-trace-go:after_create")) + + assert.NotNil(t, db.Callback().Update().Get("dd-trace-go:before_update")) + assert.NotNil(t, db.Callback().Update().Get("dd-trace-go:after_update")) + + assert.NotNil(t, db.Callback().Delete().Get("dd-trace-go:before_delete")) + assert.NotNil(t, db.Callback().Delete().Get("dd-trace-go:after_delete")) + + assert.NotNil(t, db.Callback().Query().Get("dd-trace-go:before_query")) + assert.NotNil(t, db.Callback().Query().Get("dd-trace-go:after_query")) + + assert.NotNil(t, db.Callback().Row().Get("dd-trace-go:before_row_query")) + assert.NotNil(t, db.Callback().Row().Get("dd-trace-go:after_row_query")) + + assert.NotNil(t, db.Callback().Raw().Get("dd-trace-go:before_raw_query")) + assert.NotNil(t, db.Callback().Raw().Get("dd-trace-go:before_raw_query")) +} diff --git a/contrib/graph-gophers/graphql-go/graphql.go b/contrib/graph-gophers/graphql-go/graphql.go index 08b82ac4e8..5e3b2a21eb 100644 --- a/contrib/graph-gophers/graphql-go/graphql.go +++ b/contrib/graph-gophers/graphql-go/graphql.go @@ -71,12 +71,12 @@ func (t *Tracer) TraceQuery(ctx context.Context, queryString, operationName stri } span, ctx := ddtracer.StartSpanFromContext(ctx, t.cfg.querySpanName, opts...) - ctx, request := graphqlsec.StartRequestOperation(ctx, nil, span, types.RequestOperationArgs{ + ctx, request := graphqlsec.StartRequestOperation(ctx, span, types.RequestOperationArgs{ RawQuery: queryString, OperationName: operationName, Variables: variables, }) - ctx, query := graphqlsec.StartExecutionOperation(ctx, request, span, types.ExecutionOperationArgs{ + ctx, query := graphqlsec.StartExecutionOperation(ctx, span, types.ExecutionOperationArgs{ Query: queryString, OperationName: operationName, Variables: variables, @@ -120,7 +120,7 @@ func (t *Tracer) TraceField(ctx context.Context, _, typeName, fieldName string, } span, ctx := ddtracer.StartSpanFromContext(ctx, "graphql.field", opts...) - ctx, field := graphqlsec.StartResolveOperation(ctx, graphqlsec.FromContext[*types.ExecutionOperation](ctx), span, types.ResolveOperationArgs{ + ctx, field := graphqlsec.StartResolveOperation(ctx, span, types.ResolveOperationArgs{ TypeName: typeName, FieldName: fieldName, Arguments: arguments, diff --git a/contrib/graphql-go/graphql/graphql.go b/contrib/graphql-go/graphql/graphql.go index 1db6522435..7d348fc07e 100644 --- a/contrib/graphql-go/graphql/graphql.go +++ b/contrib/graphql-go/graphql/graphql.go @@ -98,7 +98,7 @@ func (i datadogExtension) Init(ctx context.Context, params *graphql.Params) cont tracer.Tag(ext.Component, componentName), tracer.Measured(), ) - ctx, request := graphqlsec.StartRequestOperation(ctx, nil, span, types.RequestOperationArgs{ + ctx, request := graphqlsec.StartRequestOperation(ctx, span, types.RequestOperationArgs{ RawQuery: params.RequestString, Variables: params.VariableValues, OperationName: params.OperationName, @@ -193,7 +193,7 @@ func (i datadogExtension) ExecutionDidStart(ctx context.Context) (context.Contex opts = append(opts, tracer.Tag(ext.EventSampleRate, i.config.analyticsRate)) } span, ctx := tracer.StartSpanFromContext(ctx, spanExecute, opts...) - ctx, op := graphqlsec.StartExecutionOperation(ctx, graphqlsec.FromContext[*types.RequestOperation](ctx), span, types.ExecutionOperationArgs{ + ctx, op := graphqlsec.StartExecutionOperation(ctx, span, types.ExecutionOperationArgs{ Query: data.query, OperationName: data.operationName, Variables: data.variables, @@ -240,7 +240,7 @@ func (i datadogExtension) ResolveFieldDidStart(ctx context.Context, info *graphq opts = append(opts, tracer.Tag(ext.EventSampleRate, i.config.analyticsRate)) } span, ctx := tracer.StartSpanFromContext(ctx, spanResolve, opts...) - ctx, op := graphqlsec.StartResolveOperation(ctx, graphqlsec.FromContext[*types.ExecutionOperation](ctx), span, types.ResolveOperationArgs{ + ctx, op := graphqlsec.StartResolveOperation(ctx, span, types.ResolveOperationArgs{ TypeName: info.ParentType.Name(), FieldName: info.FieldName, Arguments: collectArguments(info), diff --git a/contrib/jackc/pgx.v5/metrics.go b/contrib/jackc/pgx.v5/metrics.go new file mode 100644 index 0000000000..eb94c50bfc --- /dev/null +++ b/contrib/jackc/pgx.v5/metrics.go @@ -0,0 +1,63 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2022 Datadog, Inc. + +package pgx + +import ( + "time" + + "github.com/jackc/pgx/v5/pgxpool" + "gopkg.in/DataDog/dd-trace-go.v1/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) + +const tracerPrefix = "datadog.tracer." + +const ( + AcquireCount = tracerPrefix + "pgx.pool.connections.acquire" + AcquireDuration = tracerPrefix + "pgx.pool.connections.acquire_duration" + AcquiredConns = tracerPrefix + "pgx.pool.connections.acquired_conns" + CanceledAcquireCount = tracerPrefix + "pgx.pool.connections.canceled_acquire" + ConstructingConns = tracerPrefix + "pgx.pool.connections.constructing_conns" + EmptyAcquireCount = tracerPrefix + "pgx.pool.connections.empty_acquire" + IdleConns = tracerPrefix + "pgx.pool.connections.idle_conns" + MaxConns = tracerPrefix + "pgx.pool.connections.max_conns" + TotalConns = tracerPrefix + "pgx.pool.connections.total_conns" + NewConnsCount = tracerPrefix + "pgx.pool.connections.new_conns" + MaxLifetimeDestroyCount = tracerPrefix + "pgx.pool.connections.max_lifetime_destroy" + MaxIdleDestroyCount = tracerPrefix + "pgx.pool.connections.max_idle_destroy" +) + +var interval = 10 * time.Second + +// pollPoolStats calls (*pgxpool).Stats on the pool at a predetermined interval. It pushes the pool Stats off to the statsd client. +func pollPoolStats(statsd internal.StatsdClient, pool *pgxpool.Pool) { + log.Debug("contrib/jackc/pgx.v5: Traced pool connection found: Pool stats will be gathered and sent every %v.", interval) + for range time.NewTicker(interval).C { + log.Debug("contrib/jackc/pgx.v5: Reporting pgxpool.Stat metrics...") + stat := pool.Stat() + statsd.Gauge(AcquireCount, float64(stat.AcquireCount()), []string{}, 1) + statsd.Timing(AcquireDuration, stat.AcquireDuration(), []string{}, 1) + statsd.Gauge(AcquiredConns, float64(stat.AcquiredConns()), []string{}, 1) + statsd.Gauge(CanceledAcquireCount, float64(stat.CanceledAcquireCount()), []string{}, 1) + statsd.Gauge(ConstructingConns, float64(stat.ConstructingConns()), []string{}, 1) + statsd.Gauge(EmptyAcquireCount, float64(stat.EmptyAcquireCount()), []string{}, 1) + statsd.Gauge(IdleConns, float64(stat.IdleConns()), []string{}, 1) + statsd.Gauge(MaxConns, float64(stat.MaxConns()), []string{}, 1) + statsd.Gauge(TotalConns, float64(stat.TotalConns()), []string{}, 1) + statsd.Gauge(NewConnsCount, float64(stat.NewConnsCount()), []string{}, 1) + statsd.Gauge(MaxLifetimeDestroyCount, float64(stat.MaxLifetimeDestroyCount()), []string{}, 1) + statsd.Gauge(MaxIdleDestroyCount, float64(stat.MaxIdleDestroyCount()), []string{}, 1) + } +} + +func statsTags(c *config) []string { + tags := globalconfig.StatsTags() + if c.serviceName != "" { + tags = append(tags, "service:"+c.serviceName) + } + return tags +} diff --git a/contrib/jackc/pgx.v5/option.go b/contrib/jackc/pgx.v5/option.go index 5c88bcae46..b89a3968a0 100644 --- a/contrib/jackc/pgx.v5/option.go +++ b/contrib/jackc/pgx.v5/option.go @@ -5,7 +5,12 @@ package pgx -import "gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema" +import ( + "gopkg.in/DataDog/dd-trace-go.v1/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema" +) type config struct { serviceName string @@ -14,6 +19,9 @@ type config struct { traceCopyFrom bool tracePrepare bool traceConnect bool + traceAcquire bool + poolStats bool + statsdClient internal.StatsdClient } func defaultConfig() *config { @@ -24,6 +32,22 @@ func defaultConfig() *config { traceCopyFrom: true, tracePrepare: true, traceConnect: true, + traceAcquire: true, + } +} + +// checkStatsdRequired adds a statsdClient onto the config if poolStats is enabled +// NOTE: For now, the only use-case for a statsdclient is the poolStats feature. If a statsdclient becomes necessary for other items in future work, then this logic should change +func (c *config) checkStatsdRequired() { + if c.poolStats && c.statsdClient == nil { + // contrib/jackc/pgx's statsdclient should always inherit its address from the tracer's statsdclient via the globalconfig + // destination is not user-configurable + sc, err := internal.NewStatsdClient(globalconfig.DogstatsdAddr(), statsTags(c)) + if err == nil { + c.statsdClient = sc + } else { + log.Warn("contrib/jackc/pgx.v5: Error creating statsd client; Pool stats will be dropped: %v", err) + } } } @@ -57,6 +81,13 @@ func WithTraceCopyFrom(enabled bool) Option { } } +// WithTraceAcquire enables tracing pgxpool connection acquire calls. +func WithTraceAcquire(enabled bool) Option { + return func(c *config) { + c.traceAcquire = enabled + } +} + // WithTracePrepare enables tracing prepared statements. // // conn, err := pgx.Connect(ctx, "postgres://user:pass@example.com:5432/dbname", pgx.WithTraceConnect()) @@ -81,3 +112,12 @@ func WithTraceConnect(enabled bool) Option { c.traceConnect = enabled } } + +// WithPoolStats enables polling of pgxpool.Stat metrics +// ref: https://pkg.go.dev/github.com/jackc/pgx/v5/pgxpool#Stat +// These metrics are submitted to Datadog and are not billed as custom metrics +func WithPoolStats() Option { + return func(cfg *config) { + cfg.poolStats = true + } +} diff --git a/contrib/jackc/pgx.v5/option_test.go b/contrib/jackc/pgx.v5/option_test.go new file mode 100644 index 0000000000..57741c1aa8 --- /dev/null +++ b/contrib/jackc/pgx.v5/option_test.go @@ -0,0 +1,24 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2022 Datadog, Inc. + +package pgx + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWithPoolStats(t *testing.T) { + t.Run("default off", func(t *testing.T) { + cfg := defaultConfig() + assert.False(t, cfg.poolStats) + }) + t.Run("on", func(t *testing.T) { + cfg := new(config) + WithPoolStats()(cfg) + assert.True(t, cfg.poolStats) + }) +} diff --git a/contrib/jackc/pgx.v5/pgx_tracer.go b/contrib/jackc/pgx.v5/pgx_tracer.go index fc4438c38d..f361a3ac48 100644 --- a/contrib/jackc/pgx.v5/pgx_tracer.go +++ b/contrib/jackc/pgx.v5/pgx_tracer.go @@ -7,11 +7,13 @@ package pgx import ( "context" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" ) type operationType string @@ -30,6 +32,7 @@ const ( operationTypePrepare = "Prepare" operationTypeBatch = "Batch" operationTypeCopyFrom = "Copy From" + operationTypeAcquire = "Acquire" ) type tracedBatchQuery struct { @@ -47,11 +50,12 @@ type pgxTracer struct { } var ( - _ pgx.QueryTracer = (*pgxTracer)(nil) - _ pgx.BatchTracer = (*pgxTracer)(nil) - _ pgx.ConnectTracer = (*pgxTracer)(nil) - _ pgx.PrepareTracer = (*pgxTracer)(nil) - _ pgx.CopyFromTracer = (*pgxTracer)(nil) + _ pgx.QueryTracer = (*pgxTracer)(nil) + _ pgx.BatchTracer = (*pgxTracer)(nil) + _ pgx.ConnectTracer = (*pgxTracer)(nil) + _ pgx.PrepareTracer = (*pgxTracer)(nil) + _ pgx.CopyFromTracer = (*pgxTracer)(nil) + _ pgxpool.AcquireTracer = (*pgxTracer)(nil) ) func newPgxTracer(opts ...Option) *pgxTracer { @@ -59,6 +63,7 @@ func newPgxTracer(opts ...Option) *pgxTracer { for _, opt := range opts { opt(cfg) } + cfg.checkStatsdRequired() return &pgxTracer{cfg: cfg} } @@ -175,6 +180,23 @@ func (t *pgxTracer) TraceConnectEnd(ctx context.Context, data pgx.TraceConnectEn finishSpan(ctx, data.Err) } +func (t *pgxTracer) TraceAcquireStart(ctx context.Context, pool *pgxpool.Pool, _ pgxpool.TraceAcquireStartData) context.Context { + if !t.cfg.traceAcquire { + return ctx + } + opts := t.spanOptions(pool.Config().ConnConfig, operationTypeAcquire, "") + _, ctx = tracer.StartSpanFromContext(ctx, "pgx.pool.acquire", opts...) + return ctx +} + +func (t *pgxTracer) TraceAcquireEnd(ctx context.Context, _ *pgxpool.Pool, data pgxpool.TraceAcquireEndData) { + if !t.cfg.traceAcquire { + return + } + + finishSpan(ctx, data.Err) +} + func (t *pgxTracer) spanOptions(connConfig *pgx.ConnConfig, op operationType, sqlStatement string, extraOpts ...ddtrace.StartSpanOption) []ddtrace.StartSpanOption { opts := []ddtrace.StartSpanOption{ tracer.ServiceName(t.cfg.serviceName), diff --git a/contrib/jackc/pgx.v5/pgx_tracer_test.go b/contrib/jackc/pgx.v5/pgx_tracer_test.go index 8631fd955f..b31b7f3aed 100644 --- a/contrib/jackc/pgx.v5/pgx_tracer_test.go +++ b/contrib/jackc/pgx.v5/pgx_tracer_test.go @@ -231,6 +231,29 @@ func TestCopyFrom(t *testing.T) { assert.Equal(t, ps.SpanID(), s.ParentID()) } +func TestAcquire(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + opts := append(tracingAllDisabled(), WithTraceAcquire(true)) + runAllOperations(t, opts...) + + spans := mt.FinishedSpans() + require.Len(t, spans, 5) + + ps := spans[4] + assert.Equal(t, "parent", ps.OperationName()) + assert.Equal(t, "parent", ps.Tag(ext.ResourceName)) + + s := spans[0] + assertCommonTags(t, s) + assert.Equal(t, "pgx.pool.acquire", s.OperationName()) + assert.Equal(t, "Acquire", s.Tag(ext.ResourceName)) + assert.Equal(t, "Acquire", s.Tag("db.operation")) + assert.Equal(t, nil, s.Tag(ext.DBStatement)) + assert.Equal(t, ps.SpanID(), s.ParentID()) +} + func tracingAllDisabled() []Option { return []Option{ WithTraceConnect(false), @@ -238,6 +261,7 @@ func tracingAllDisabled() []Option { WithTracePrepare(false), WithTraceBatch(false), WithTraceCopyFrom(false), + WithTraceAcquire(false), } } @@ -246,9 +270,9 @@ func runAllOperations(t *testing.T, opts ...Option) { defer parent.Finish() // Connect - conn, err := Connect(ctx, postgresDSN, opts...) + conn, err := NewPool(ctx, postgresDSN, opts...) require.NoError(t, err) - defer conn.Close(ctx) + defer conn.Close() // Query var x int diff --git a/contrib/jackc/pgx.v5/pgxpool.go b/contrib/jackc/pgx.v5/pgxpool.go index f61a162948..f4a8cb7ed0 100644 --- a/contrib/jackc/pgx.v5/pgxpool.go +++ b/contrib/jackc/pgx.v5/pgxpool.go @@ -20,6 +20,14 @@ func NewPool(ctx context.Context, connString string, opts ...Option) (*pgxpool.P } func NewPoolWithConfig(ctx context.Context, config *pgxpool.Config, opts ...Option) (*pgxpool.Pool, error) { - config.ConnConfig.Tracer = newPgxTracer(opts...) - return pgxpool.NewWithConfig(ctx, config) + tracer := newPgxTracer(opts...) + config.ConnConfig.Tracer = tracer + pool, err := pgxpool.NewWithConfig(ctx, config) + if err != nil { + return nil, err + } + if tracer.cfg.poolStats && tracer.cfg.statsdClient != nil { + go pollPoolStats(tracer.cfg.statsdClient, pool) + } + return pool, nil } diff --git a/contrib/jackc/pgx.v5/pgxpool_test.go b/contrib/jackc/pgx.v5/pgxpool_test.go index d8ce0b3bae..c784da33a3 100644 --- a/contrib/jackc/pgx.v5/pgxpool_test.go +++ b/contrib/jackc/pgx.v5/pgxpool_test.go @@ -8,8 +8,11 @@ package pgx import ( "context" "testing" + "time" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/statsdtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -36,5 +39,35 @@ func TestPool(t *testing.T) { assert.Equal(t, 2, x) assert.Len(t, mt.OpenSpans(), 0) - assert.Len(t, mt.FinishedSpans(), 5) + assert.Len(t, mt.FinishedSpans(), 7) +} + +func TestPoolWithPoolStats(t *testing.T) { + originalInterval := interval + interval = 1 * time.Millisecond + t.Cleanup(func() { + interval = originalInterval + }) + + ctx := context.Background() + statsd := new(statsdtest.TestStatsdClient) + conn, err := NewPool(ctx, postgresDSN, withStatsdClient(statsd), WithPoolStats()) + require.NoError(t, err) + defer conn.Close() + + wantStats := []string{AcquireCount, AcquireDuration, AcquiredConns, CanceledAcquireCount, ConstructingConns, EmptyAcquireCount, IdleConns, MaxConns, TotalConns, NewConnsCount, MaxLifetimeDestroyCount, MaxIdleDestroyCount} + + assert := assert.New(t) + if err := statsd.Wait(assert, len(wantStats), time.Second); err != nil { + t.Fatalf("statsd.Wait(): %v", err) + } + for _, name := range wantStats { + assert.Contains(statsd.CallNames(), name) + } +} + +func withStatsdClient(s internal.StatsdClient) Option { + return func(c *config) { + c.statsdClient = s + } } diff --git a/contrib/log/slog/example_test.go b/contrib/log/slog/example_test.go new file mode 100644 index 0000000000..0e5683b897 --- /dev/null +++ b/contrib/log/slog/example_test.go @@ -0,0 +1,48 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package slog_test + +import ( + "context" + "log/slog" + "os" + + slogtrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/log/slog" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +func ExampleNewJSONHandler() { + // start the DataDog tracer + tracer.Start() + defer tracer.Stop() + + // create the application logger + logger := slog.New(slogtrace.NewJSONHandler(os.Stdout, nil)) + + // start a new span + span, ctx := tracer.StartSpanFromContext(context.Background(), "ExampleNewJSONHandler") + defer span.Finish() + + // log a message using the context containing span information + logger.Log(ctx, slog.LevelInfo, "this is a log with tracing information") +} + +func ExampleWrapHandler() { + // start the DataDog tracer + tracer.Start() + defer tracer.Stop() + + // create the application logger + myHandler := slog.NewJSONHandler(os.Stdout, nil) + logger := slog.New(slogtrace.WrapHandler(myHandler)) + + // start a new span + span, ctx := tracer.StartSpanFromContext(context.Background(), "ExampleWrapHandler") + defer span.Finish() + + // log a message using the context containing span information + logger.Log(ctx, slog.LevelInfo, "this is a log with tracing information") +} diff --git a/contrib/log/slog/slog.go b/contrib/log/slog/slog.go new file mode 100644 index 0000000000..1a27186a27 --- /dev/null +++ b/contrib/log/slog/slog.go @@ -0,0 +1,51 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +// Package slog provides functions to correlate logs and traces using log/slog package (https://pkg.go.dev/log/slog). +package slog // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/log/slog" + +import ( + "context" + "io" + "log/slog" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" +) + +const componentName = "log/slog" + +func init() { + telemetry.LoadIntegration(componentName) + tracer.MarkIntegrationImported("log/slog") +} + +// NewJSONHandler is a convenience function that returns a *slog.JSONHandler logger enhanced with +// tracing information. +func NewJSONHandler(w io.Writer, opts *slog.HandlerOptions) slog.Handler { + return WrapHandler(slog.NewJSONHandler(w, opts)) +} + +// WrapHandler enhances the given logger handler attaching tracing information to logs. +func WrapHandler(h slog.Handler) slog.Handler { + return &handler{h} +} + +type handler struct { + slog.Handler +} + +// Handle handles the given Record, attaching tracing information if found. +func (h *handler) Handle(ctx context.Context, rec slog.Record) error { + span, ok := tracer.SpanFromContext(ctx) + if ok { + rec.Add( + slog.Uint64(ext.LogKeyTraceID, span.Context().TraceID()), + slog.Uint64(ext.LogKeySpanID, span.Context().SpanID()), + ) + } + return h.Handler.Handle(ctx, rec) +} diff --git a/contrib/log/slog/slog_test.go b/contrib/log/slog/slog_test.go new file mode 100644 index 0000000000..5b74691469 --- /dev/null +++ b/contrib/log/slog/slog_test.go @@ -0,0 +1,76 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package slog + +import ( + "bytes" + "context" + "encoding/json" + "log/slog" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + internallog "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) + +func assertLogEntry(t *testing.T, rawEntry, wantMsg, wantLevel string) { + t.Helper() + + var data map[string]interface{} + err := json.Unmarshal([]byte(rawEntry), &data) + require.NoError(t, err) + require.NotEmpty(t, data) + + assert.Equal(t, wantMsg, data["msg"]) + assert.Equal(t, wantLevel, data["level"]) + assert.NotEmpty(t, data["time"]) + assert.NotEmpty(t, data[ext.LogKeyTraceID]) + assert.NotEmpty(t, data[ext.LogKeySpanID]) +} + +func testLogger(t *testing.T, createHandler func(b *bytes.Buffer) slog.Handler) { + tracer.Start(tracer.WithLogger(internallog.DiscardLogger{})) + defer tracer.Stop() + + // create the application logger + var b bytes.Buffer + h := createHandler(&b) + logger := slog.New(h) + + // start a new span + span, ctx := tracer.StartSpanFromContext(context.Background(), "test") + defer span.Finish() + + // log a message using the context containing span information + logger.Log(ctx, slog.LevelInfo, "this is an info log with tracing information") + logger.Log(ctx, slog.LevelError, "this is an error log with tracing information") + + logs := strings.Split( + strings.TrimRight(b.String(), "\n"), + "\n", + ) + // assert log entries contain trace information + require.Len(t, logs, 2) + assertLogEntry(t, logs[0], "this is an info log with tracing information", "INFO") + assertLogEntry(t, logs[1], "this is an error log with tracing information", "ERROR") +} + +func TestNewJSONHandler(t *testing.T) { + testLogger(t, func(b *bytes.Buffer) slog.Handler { + return NewJSONHandler(b, nil) + }) +} + +func TestWrapHandler(t *testing.T) { + testLogger(t, func(b *bytes.Buffer) slog.Handler { + return WrapHandler(slog.NewJSONHandler(b, nil)) + }) +} diff --git a/contrib/net/http/make_responsewriter.go b/contrib/net/http/make_responsewriter.go index b9ff8a2e13..13ac9a8a14 100644 --- a/contrib/net/http/make_responsewriter.go +++ b/contrib/net/http/make_responsewriter.go @@ -52,6 +52,10 @@ import "net/http" // // This code is generated because we have to account for all the permutations // of the interfaces. +// +// In case of any new interfaces or methods we didn't consider here, we also +// implement the rwUnwrapper interface, which is used internally by +// the standard library: https://github.com/golang/go/blob/6d89b38ed86e0bfa0ddaba08dc4071e6bb300eea/src/net/http/responsecontroller.go#L42-L44 func wrapResponseWriter(w http.ResponseWriter) (http.ResponseWriter, *responseWriter) { {{- range .Interfaces }} h{{.}}, ok{{.}} := w.(http.{{.}}) @@ -61,6 +65,7 @@ func wrapResponseWriter(w http.ResponseWriter) (http.ResponseWriter, *responseWr type monitoredResponseWriter interface { http.ResponseWriter Status() int + Unwrap() http.ResponseWriter } switch { {{- range .Combinations }} diff --git a/contrib/net/http/roundtripper_test.go b/contrib/net/http/roundtripper_test.go index dd7c9301be..bc9f590fb3 100644 --- a/contrib/net/http/roundtripper_test.go +++ b/contrib/net/http/roundtripper_test.go @@ -662,7 +662,7 @@ func TestAppsec(t *testing.T) { resp, err := client.RoundTrip(req.WithContext(r.Context())) if enabled { - require.ErrorIs(t, err, &events.BlockingSecurityEvent{}) + require.True(t, events.IsSecurityError(err)) } else { require.NoError(t, err) } @@ -690,6 +690,7 @@ func TestAppsec(t *testing.T) { require.Contains(t, appsecJSON, httpsec.ServerIoNetURLAddr) require.Contains(t, serviceSpan.Tags(), "_dd.stack") + require.NotContains(t, serviceSpan.Tags(), "error.message") // This is a nested event so it should contain the child span id in the service entry span // TODO(eliott.bouhana): uncomment this once we have the child span id in the service entry span diff --git a/contrib/net/http/trace.go b/contrib/net/http/trace.go index a019f5819e..b04867c931 100644 --- a/contrib/net/http/trace.go +++ b/contrib/net/http/trace.go @@ -111,3 +111,8 @@ func (w *responseWriter) WriteHeader(status int) { w.ResponseWriter.WriteHeader(status) w.status = status } + +// Unwrap returns the underlying wrapped http.ResponseWriter. +func (w *responseWriter) Unwrap() http.ResponseWriter { + return w.ResponseWriter +} diff --git a/contrib/net/http/trace_gen.go b/contrib/net/http/trace_gen.go index 728dac8694..db04144454 100644 --- a/contrib/net/http/trace_gen.go +++ b/contrib/net/http/trace_gen.go @@ -17,6 +17,10 @@ import "net/http" // // This code is generated because we have to account for all the permutations // of the interfaces. +// +// In case of any new interfaces or methods we didn't consider here, we also +// implement the rwUnwrapper interface, which is used internally by +// the standard library: https://github.com/golang/go/blob/6d89b38ed86e0bfa0ddaba08dc4071e6bb300eea/src/net/http/responsecontroller.go#L42-L44 func wrapResponseWriter(w http.ResponseWriter) (http.ResponseWriter, *responseWriter) { hFlusher, okFlusher := w.(http.Flusher) hPusher, okPusher := w.(http.Pusher) @@ -27,6 +31,7 @@ func wrapResponseWriter(w http.ResponseWriter) (http.ResponseWriter, *responseWr type monitoredResponseWriter interface { http.ResponseWriter Status() int + Unwrap() http.ResponseWriter } switch { case okFlusher && okPusher && okCloseNotifier && okHijacker: diff --git a/contrib/net/http/trace_test.go b/contrib/net/http/trace_test.go index f7018976a8..fe8e062973 100644 --- a/contrib/net/http/trace_test.go +++ b/contrib/net/http/trace_test.go @@ -11,6 +11,7 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" @@ -18,6 +19,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestTraceAndServe(t *testing.T) { @@ -357,6 +359,48 @@ func TestTraceAndServeHost(t *testing.T) { }) } +// TestUnwrap tests the implementation of the rwUnwrapper interface, which is used internally +// by the standard library: https://github.com/golang/go/blob/6d89b38ed86e0bfa0ddaba08dc4071e6bb300eea/src/net/http/responsecontroller.go#L42-L44 +// See also: https://github.com/DataDog/dd-trace-go/issues/2674 +func TestUnwrap(t *testing.T) { + h := WrapHandler(deadlineHandler, "service-name", "resource-name") + srv := httptest.NewServer(h) + defer srv.Close() + + resp, err := srv.Client().Get(srv.URL + "/") + require.NoError(t, err) + defer resp.Body.Close() + + b, err := io.ReadAll(resp.Body) + require.NoError(t, err) + respText := string(b) + + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, "OK", respText) +} + +var deadlineHandler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + rc := http.NewResponseController(w) + + // Use the SetReadDeadline and SetWriteDeadline methods, which are not explicitly implemented in the trace_gen.go + // generated file. Before the Unwrap change, these methods returned a "feature not supported" error. + + err := rc.SetReadDeadline(time.Now().Add(10 * time.Second)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + err = rc.SetWriteDeadline(time.Now().Add(10 * time.Second)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) +}) + type noopHandler struct{} func (noopHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {} diff --git a/contrib/sirupsen/logrus/logrus.go b/contrib/sirupsen/logrus/logrus.go index cce5dd43f4..d0e379d796 100644 --- a/contrib/sirupsen/logrus/logrus.go +++ b/contrib/sirupsen/logrus/logrus.go @@ -7,6 +7,7 @@ package logrus import ( + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" @@ -34,7 +35,7 @@ func (d *DDContextLogHook) Fire(e *logrus.Entry) error { if !found { return nil } - e.Data["dd.trace_id"] = span.Context().TraceID() - e.Data["dd.span_id"] = span.Context().SpanID() + e.Data[ext.LogKeyTraceID] = span.Context().TraceID() + e.Data[ext.LogKeySpanID] = span.Context().SpanID() return nil } diff --git a/contrib/uptrace/bun/bun.go b/contrib/uptrace/bun/bun.go new file mode 100644 index 0000000000..9384806d65 --- /dev/null +++ b/contrib/uptrace/bun/bun.go @@ -0,0 +1,82 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +// Package bun provides helper functions for tracing the github.com/uptrace/bun package (https://github.com/uptrace/bun). +package bun + +import ( + "context" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry" +) + +const ( + componentName = "uptrace/bun" + defaultServiceName = "bun.db" +) + +func init() { + telemetry.LoadIntegration(componentName) + tracer.MarkIntegrationImported("github.com/uptrace/bun") +} + +// Wrap augments the given DB with tracing. +func Wrap(db *bun.DB, opts ...Option) { + cfg := new(config) + defaults(cfg) + for _, opt := range opts { + opt(cfg) + } + log.Debug("contrib/uptrace/bun: Wrapping Database") + db.AddQueryHook(&queryHook{cfg: cfg}) +} + +type queryHook struct { + cfg *config +} + +var _ bun.QueryHook = (*queryHook)(nil) + +// BeforeQuery starts a span before a query is executed. +func (qh *queryHook) BeforeQuery(ctx context.Context, qe *bun.QueryEvent) context.Context { + var dbSystem string + switch qe.DB.Dialect().Name() { + case dialect.PG: + dbSystem = ext.DBSystemPostgreSQL + case dialect.MySQL: + dbSystem = ext.DBSystemMySQL + case dialect.MSSQL: + dbSystem = ext.DBSystemMicrosoftSQLServer + default: + dbSystem = ext.DBSystemOtherSQL + } + var ( + query = qe.Query + opts = []ddtrace.StartSpanOption{ + tracer.SpanType(ext.SpanTypeSQL), + tracer.ResourceName(string(query)), + tracer.ServiceName(qh.cfg.serviceName), + tracer.Tag(ext.Component, componentName), + tracer.Tag(ext.DBSystem, dbSystem), + } + ) + _, ctx = tracer.StartSpanFromContext(ctx, "bun.query", opts...) + return ctx +} + +// AfterQuery finishes a span when a query returns. +func (qh *queryHook) AfterQuery(ctx context.Context, qe *bun.QueryEvent) { + span, ok := tracer.SpanFromContext(ctx) + if !ok { + return + } + span.Finish(tracer.WithError(qe.Err)) +} diff --git a/contrib/uptrace/bun/bun_test.go b/contrib/uptrace/bun/bun_test.go new file mode 100644 index 0000000000..998aab18f8 --- /dev/null +++ b/contrib/uptrace/bun/bun_test.go @@ -0,0 +1,216 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package bun + +import ( + "context" + "database/sql" + "fmt" + "os" + "testing" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + + _ "github.com/go-sql-driver/mysql" + _ "github.com/lib/pq" + _ "github.com/microsoft/go-mssqldb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/sqlitedialect" + _ "modernc.org/sqlite" +) + +func TestMain(m *testing.M) { + _, ok := os.LookupEnv("INTEGRATION") + if !ok { + fmt.Println("--- SKIP: to enable integration test, set the INTEGRATION environment variable") + os.Exit(0) + } + os.Exit(m.Run()) +} + +func setupDB(t *testing.T, driverName, dataSourceName string, opts ...Option) *bun.DB { + t.Helper() + sqlite, err := sql.Open(driverName, dataSourceName) + if err != nil { + t.Fatal(err) + } + + db := bun.NewDB(sqlite, sqlitedialect.New()) + Wrap(db, opts...) + + return db +} + +func TestImplementsHook(_ *testing.T) { + var _ bun.QueryHook = (*queryHook)(nil) +} + +func TestSelect(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + tC := []struct { + name string + driver string + dataSource string + expected string + }{ + { + name: "SQLite", + driver: "sqlite", + dataSource: "file::memory:?cache=shared", + expected: ext.DBSystemOtherSQL, + }, + { + name: "Postgres", + driver: "postgres", + dataSource: "postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable", + expected: ext.DBSystemPostgreSQL, + }, + { + name: "MySQL", + driver: "mysql", + dataSource: "test:test@tcp(127.0.0.1:3306)/test", + expected: ext.DBSystemMySQL, + }, + { + name: "MSSQL", + driver: "sqlserver", + dataSource: "sqlserver://sa:myPassw0rd@127.0.0.1:1433?database=master", + expected: ext.DBSystemMicrosoftSQLServer, + }, + } + for _, tt := range tC { + tt := tt + t.Run(tt.name, func(t *testing.T) { + db := setupDB(t, tt.driver, tt.dataSource) + parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "http.request", + tracer.ServiceName("fake-http-server"), + tracer.SpanType(ext.SpanTypeWeb), + ) + + var n, rows int64 + res, err := db.NewSelect().ColumnExpr("1").Exec(ctx, &n) + parentSpan.Finish() + spans := mt.FinishedSpans() + + require.NoError(t, err) + rows, _ = res.RowsAffected() + assert.Equal(int64(1), rows) + assert.Equal(2, len(spans)) + assert.Equal(nil, err) + assert.Equal(int64(1), n) + assert.Equal("bun.query", spans[0].OperationName()) + assert.Equal("http.request", spans[1].OperationName()) + assert.Equal("uptrace/bun", spans[0].Tag(ext.Component)) + assert.Equal(ext.DBSystemOtherSQL, spans[0].Tag(ext.DBSystem)) + mt.Reset() + }) + } +} + +func TestServiceName(t *testing.T) { + t.Run("default", func(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + db := setupDB(t, "sqlite", "file::memory:?cache=shared") + parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "http.request", + tracer.ServiceName("fake-http-server"), + tracer.SpanType(ext.SpanTypeWeb), + ) + + var n int + res, err := db.NewSelect().ColumnExpr("1").Exec(ctx, &n) + parentSpan.Finish() + spans := mt.FinishedSpans() + + require.NoError(t, err) + rows, _ := res.RowsAffected() + assert.Equal(int64(1), rows) + assert.Len(spans, 2) + assert.Equal(nil, err) + assert.Equal(1, n) + assert.Equal("bun.query", spans[0].OperationName()) + assert.Equal("http.request", spans[1].OperationName()) + assert.Equal("bun.db", spans[0].Tag(ext.ServiceName)) + assert.Equal("fake-http-server", spans[1].Tag(ext.ServiceName)) + assert.Equal("uptrace/bun", spans[0].Tag(ext.Component)) + assert.Equal(ext.DBSystemOtherSQL, spans[0].Tag(ext.DBSystem)) + assert.Equal(spans[0].ParentID(), spans[1].SpanID()) + }) + + t.Run("global", func(t *testing.T) { + prevName := globalconfig.ServiceName() + defer globalconfig.SetServiceName(prevName) + globalconfig.SetServiceName("global-service") + + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + db := setupDB(t, "sqlite", "file::memory:?cache=shared") + parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "http.request", + tracer.ServiceName("fake-http-server"), + tracer.SpanType(ext.SpanTypeWeb), + ) + + var n int + res, err := db.NewSelect().ColumnExpr("1").Exec(ctx, &n) + parentSpan.Finish() + spans := mt.FinishedSpans() + + require.NoError(t, err) + rows, _ := res.RowsAffected() + assert.Equal(int64(1), rows) + assert.Equal(2, len(spans)) + assert.Equal(nil, err) + assert.Equal(1, n) + assert.Equal("bun.query", spans[0].OperationName()) + assert.Equal("http.request", spans[1].OperationName()) + assert.Equal("global-service", spans[0].Tag(ext.ServiceName)) + assert.Equal("fake-http-server", spans[1].Tag(ext.ServiceName)) + assert.Equal("uptrace/bun", spans[0].Tag(ext.Component)) + assert.Equal(ext.DBSystemOtherSQL, spans[0].Tag(ext.DBSystem)) + }) + + t.Run("custom", func(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + db := setupDB(t, "sqlite", "file::memory:?cache=shared", WithService("my-service-name")) + parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "http.request", + tracer.ServiceName("fake-http-server"), + tracer.SpanType(ext.SpanTypeWeb), + ) + + var n int + res, err := db.NewSelect().ColumnExpr("1").Exec(ctx, &n) + parentSpan.Finish() + spans := mt.FinishedSpans() + + require.NoError(t, err) + rows, _ := res.RowsAffected() + assert.Equal(int64(1), rows) + assert.Equal(2, len(spans)) + assert.Equal(nil, err) + assert.Equal(1, n) + assert.Equal("bun.query", spans[0].OperationName()) + assert.Equal("http.request", spans[1].OperationName()) + assert.Equal("my-service-name", spans[0].Tag(ext.ServiceName)) + assert.Equal("fake-http-server", spans[1].Tag(ext.ServiceName)) + assert.Equal("uptrace/bun", spans[0].Tag(ext.Component)) + assert.Equal(ext.DBSystemOtherSQL, spans[0].Tag(ext.DBSystem)) + }) +} diff --git a/contrib/uptrace/bun/example_test.go b/contrib/uptrace/bun/example_test.go new file mode 100644 index 0000000000..4c29f6c71b --- /dev/null +++ b/contrib/uptrace/bun/example_test.go @@ -0,0 +1,31 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package bun_test + +import ( + "context" + "database/sql" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/sqlitedialect" + buntrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/uptrace/bun" + _ "modernc.org/sqlite" +) + +func Example() { + sqlite, err := sql.Open("sqlite", "file::memory:?cache=shared") + if err != nil { + panic(err) + } + db := bun.NewDB(sqlite, sqlitedialect.New()) + + // Wrap the connection with the APM hook. + buntrace.Wrap(db) + var user struct { + Name string + } + _ = db.NewSelect().Column("name").Table("users").Scan(context.Background(), &user) +} diff --git a/contrib/uptrace/bun/option.go b/contrib/uptrace/bun/option.go new file mode 100644 index 0000000000..586778671a --- /dev/null +++ b/contrib/uptrace/bun/option.go @@ -0,0 +1,32 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package bun + +import ( + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" +) + +type config struct { + serviceName string +} + +// Option represents an option that can be used to create or wrap a client. +type Option func(*config) + +func defaults(cfg *config) { + service := defaultServiceName + if svc := globalconfig.ServiceName(); svc != "" { + service = svc + } + cfg.serviceName = service +} + +// WithService sets the given service name for the client. +func WithService(name string) Option { + return func(cfg *config) { + cfg.serviceName = name + } +} diff --git a/ddtrace/ext/db.go b/ddtrace/ext/db.go index 8074342d17..f729f237b0 100644 --- a/ddtrace/ext/db.go +++ b/ddtrace/ext/db.go @@ -73,6 +73,9 @@ const ( // CassandraCluster specifies the tag name that is used to set the cluster. CassandraCluster = "cassandra.cluster" + // CassandraDatacenter specifies the tag name that is used to set the datacenter. + CassandraDatacenter = "cassandra.datacenter" + // CassandraRowCount specifies the tag name to use when settings the row count. CassandraRowCount = "cassandra.row_count" diff --git a/ddtrace/ext/log_key.go b/ddtrace/ext/log_key.go new file mode 100644 index 0000000000..b17e098ffa --- /dev/null +++ b/ddtrace/ext/log_key.go @@ -0,0 +1,13 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package ext + +const ( + // LogKeyTraceID is used by log integrations to correlate logs with a given trace. + LogKeyTraceID = "dd.trace_id" + // LogKeySpanID is used by log integrations to correlate logs with a given span. + LogKeySpanID = "dd.span_id" +) diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 0a4252bfd1..835a3e1802 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -80,7 +80,7 @@ func newMockTracer() *mocktracer { client := &http.Client{ Transport: t.dsmTransport, } - t.dsmProcessor = datastreams.NewProcessor(&statsd.NoOpClient{}, "env", "service", "v1", &url.URL{Scheme: "http", Host: "agent-address"}, client, func() bool { return true }) + t.dsmProcessor = datastreams.NewProcessor(&statsd.NoOpClient{}, "env", "service", "v1", &url.URL{Scheme: "http", Host: "agent-address"}, client) t.dsmProcessor.Start() t.dsmProcessor.Flush() return &t diff --git a/ddtrace/tracer/civisibility_payload.go b/ddtrace/tracer/civisibility_payload.go new file mode 100644 index 0000000000..df8ffc04cc --- /dev/null +++ b/ddtrace/tracer/civisibility_payload.go @@ -0,0 +1,117 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package tracer + +import ( + "bytes" + "sync/atomic" + + "github.com/tinylib/msgp/msgp" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + "gopkg.in/DataDog/dd-trace-go.v1/internal/version" +) + +// ciVisibilityPayload represents a payload specifically designed for CI Visibility events. +// It embeds the generic payload structure and adds methods to handle CI Visibility specific data. +type ciVisibilityPayload struct { + *payload +} + +// push adds a new CI Visibility event to the payload buffer. +// It grows the buffer to accommodate the new event, encodes the event in MessagePack format, and updates the event count. +// +// Parameters: +// +// event - The CI Visibility event to be added to the payload. +// +// Returns: +// +// An error if encoding the event fails. +func (p *ciVisibilityPayload) push(event *ciVisibilityEvent) error { + p.buf.Grow(event.Msgsize()) + if err := msgp.Encode(&p.buf, event); err != nil { + return err + } + atomic.AddUint32(&p.count, 1) + p.updateHeader() + return nil +} + +// newCiVisibilityPayload creates a new instance of civisibilitypayload. +// +// Returns: +// +// A pointer to a newly initialized civisibilitypayload instance. +func newCiVisibilityPayload() *ciVisibilityPayload { + return &ciVisibilityPayload{newPayload()} +} + +// getBuffer retrieves the complete body of the CI Visibility payload, including metadata. +// It reads the current payload buffer, adds metadata, and encodes the entire payload in MessagePack format. +// +// Parameters: +// +// config - A pointer to the config structure containing environment settings. +// +// Returns: +// +// A pointer to a bytes.Buffer containing the encoded CI Visibility payload. +// An error if reading from the buffer or encoding the payload fails. +func (p *ciVisibilityPayload) getBuffer(config *config) (*bytes.Buffer, error) { + + /* + The Payload format in the CI Visibility protocol is like this: + { + "version": 1, + "metadata": { + "*": { + "runtime-id": "...", + "language": "...", + "library_version": "...", + "env": "..." + } + }, + "events": [ + // ... + ] + } + + The event format can be found in the `civisibility_tslv.go` file in the ciVisibilityEvent documentation + */ + + // Create a buffer to read the current payload + payloadBuf := new(bytes.Buffer) + if _, err := payloadBuf.ReadFrom(p.payload); err != nil { + return nil, err + } + + // Create the metadata map + allMetadata := map[string]string{ + "language": "go", + "runtime-id": globalconfig.RuntimeID(), + "library_version": version.Tag, + } + if config.env != "" { + allMetadata["env"] = config.env + } + + // Create the visibility payload + visibilityPayload := ciTestCyclePayload{ + Version: 1, + Metadata: map[string]map[string]string{ + "*": allMetadata, + }, + Events: payloadBuf.Bytes(), + } + + // Create a new buffer to encode the visibility payload in MessagePack format + encodedBuf := new(bytes.Buffer) + if err := msgp.Encode(encodedBuf, &visibilityPayload); err != nil { + return nil, err + } + + return encodedBuf, nil +} diff --git a/ddtrace/tracer/civisibility_payload_test.go b/ddtrace/tracer/civisibility_payload_test.go new file mode 100644 index 0000000000..4057bb36e7 --- /dev/null +++ b/ddtrace/tracer/civisibility_payload_test.go @@ -0,0 +1,120 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package tracer + +import ( + "bytes" + "io" + "strconv" + "strings" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tinylib/msgp/msgp" +) + +func newCiVisibilityEventsList(n int) []*ciVisibilityEvent { + list := make([]*ciVisibilityEvent, n) + for i := 0; i < n; i++ { + s := newBasicSpan("span.list." + strconv.Itoa(i%5+1)) + s.Start = fixedTime + list[i] = getCiVisibilityEvent(s) + } + + return list +} + +// TestCiVisibilityPayloadIntegrity tests that whatever we push into the payload +// allows us to read the same content as would have been encoded by +// the codec. +func TestCiVisibilityPayloadIntegrity(t *testing.T) { + want := new(bytes.Buffer) + for _, n := range []int{10, 1 << 10, 1 << 17} { + t.Run(strconv.Itoa(n), func(t *testing.T) { + assert := assert.New(t) + p := newCiVisibilityPayload() + var allEvents ciVisibilityEvents + + for i := 0; i < n; i++ { + list := newCiVisibilityEventsList(i%5 + 1) + allEvents = append(allEvents, list...) + for _, event := range list { + p.push(event) + } + } + + want.Reset() + err := msgp.Encode(want, allEvents) + assert.NoError(err) + assert.Equal(want.Len(), p.size()) + assert.Equal(p.itemCount(), len(allEvents)) + + got, err := io.ReadAll(p) + assert.NoError(err) + assert.Equal(want.Bytes(), got) + }) + } +} + +// TestCiVisibilityPayloadDecode ensures that whatever we push into the payload can +// be decoded by the codec. +func TestCiVisibilityPayloadDecode(t *testing.T) { + assert := assert.New(t) + for _, n := range []int{10, 1 << 10} { + t.Run(strconv.Itoa(n), func(t *testing.T) { + p := newCiVisibilityPayload() + for i := 0; i < n; i++ { + list := newCiVisibilityEventsList(i%5 + 1) + for _, event := range list { + p.push(event) + } + } + var got ciVisibilityEvents + err := msgp.Decode(p, &got) + assert.NoError(err) + }) + } +} + +func BenchmarkCiVisibilityPayloadThroughput(b *testing.B) { + b.Run("10K", benchmarkCiVisibilityPayloadThroughput(1)) + b.Run("100K", benchmarkCiVisibilityPayloadThroughput(10)) + b.Run("1MB", benchmarkCiVisibilityPayloadThroughput(100)) +} + +// benchmarkCiVisibilityPayloadThroughput benchmarks the throughput of the payload by subsequently +// pushing a list of civisibility events containing count spans of approximately 10KB in size each, until the +// payload is filled. +func benchmarkCiVisibilityPayloadThroughput(count int) func(*testing.B) { + return func(b *testing.B) { + p := newCiVisibilityPayload() + s := newBasicSpan("X") + s.Meta["key"] = strings.Repeat("X", 10*1024) + e := getCiVisibilityEvent(s) + events := make(ciVisibilityEvents, count) + for i := 0; i < count; i++ { + events[i] = e + } + + b.ReportAllocs() + b.ResetTimer() + reset := func() { + p.header = make([]byte, 8) + p.off = 8 + atomic.StoreUint32(&p.count, 0) + p.buf.Reset() + } + for i := 0; i < b.N; i++ { + reset() + for _, event := range events { + for p.size() < payloadMaxLimit { + p.push(event) + } + } + } + } +} diff --git a/ddtrace/tracer/civisibility_transport.go b/ddtrace/tracer/civisibility_transport.go new file mode 100644 index 0000000000..db64b5d73d --- /dev/null +++ b/ddtrace/tracer/civisibility_transport.go @@ -0,0 +1,200 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package tracer + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "net/http" + "os" + "runtime" + "strconv" + "strings" + + "gopkg.in/DataDog/dd-trace-go.v1/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/version" +) + +// Constants for CI Visibility API paths and subdomains. +const ( + TestCycleSubdomain = "citestcycle-intake" // Subdomain for test cycle intake. + TestCyclePath = "api/v2/citestcycle" // API path for test cycle. + EvpProxyPath = "evp_proxy/v2" // Path for EVP proxy. +) + +// Ensure that civisibilityTransport implements the transport interface. +var _ transport = (*ciVisibilityTransport)(nil) + +// ciVisibilityTransport is a structure that handles sending CI Visibility payloads +// to the Datadog endpoint, either in agentless mode or through the EVP proxy. +type ciVisibilityTransport struct { + config *config // Configuration for the tracer. + testCycleURLPath string // URL path for the test cycle endpoint. + headers map[string]string // HTTP headers to be included in the requests. + agentless bool // Gets if the transport is configured in agentless mode (eg: Gzip support) +} + +// newCiVisibilityTransport creates and initializes a new civisibilityTransport +// based on the provided tracer configuration. It sets up the appropriate headers +// and determines the URL path based on whether agentless mode is enabled. +// +// Parameters: +// +// config - The tracer configuration. +// +// Returns: +// +// A pointer to an initialized civisibilityTransport instance. +func newCiVisibilityTransport(config *config) *ciVisibilityTransport { + // Initialize the default headers with encoder metadata. + defaultHeaders := map[string]string{ + "Datadog-Meta-Lang": "go", + "Datadog-Meta-Lang-Version": strings.TrimPrefix(runtime.Version(), "go"), + "Datadog-Meta-Lang-Interpreter": runtime.Compiler + "-" + runtime.GOARCH + "-" + runtime.GOOS, + "Datadog-Meta-Tracer-Version": version.Tag, + "Content-Type": "application/msgpack", + } + if cid := internal.ContainerID(); cid != "" { + defaultHeaders["Datadog-Container-ID"] = cid + } + if eid := internal.EntityID(); eid != "" { + defaultHeaders["Datadog-Entity-ID"] = eid + } + + // Determine if agentless mode is enabled through an environment variable. + agentlessEnabled := internal.BoolEnv(constants.CIVisibilityAgentlessEnabledEnvironmentVariable, false) + + testCycleURL := "" + if agentlessEnabled { + // Agentless mode is enabled. + APIKeyValue := os.Getenv(constants.APIKeyEnvironmentVariable) + if APIKeyValue == "" { + log.Error("An API key is required for agentless mode. Use the DD_API_KEY env variable to set it") + } + + defaultHeaders["dd-api-key"] = APIKeyValue + + // Check for a custom agentless URL. + agentlessURL := "" + if v := os.Getenv(constants.CIVisibilityAgentlessURLEnvironmentVariable); v != "" { + agentlessURL = v + } + + if agentlessURL == "" { + // Use the standard agentless URL format. + site := "datadoghq.com" + if v := os.Getenv("DD_SITE"); v != "" { + site = v + } + + testCycleURL = fmt.Sprintf("https://%s.%s/%s", TestCycleSubdomain, site, TestCyclePath) + } else { + // Use the custom agentless URL. + testCycleURL = fmt.Sprintf("%s/%s", agentlessURL, TestCyclePath) + } + } else { + // Use agent mode with the EVP proxy. + defaultHeaders["X-Datadog-EVP-Subdomain"] = TestCycleSubdomain + testCycleURL = fmt.Sprintf("%s/%s/%s", config.agentURL.String(), EvpProxyPath, TestCyclePath) + } + + return &ciVisibilityTransport{ + config: config, + testCycleURLPath: testCycleURL, + headers: defaultHeaders, + agentless: agentlessEnabled, + } +} + +// send sends the CI Visibility payload to the Datadog endpoint. +// It prepares the payload, creates the HTTP request, and handles the response. +// +// Parameters: +// +// p - The payload to be sent. +// +// Returns: +// +// An io.ReadCloser for reading the response body, and an error if the operation fails. +func (t *ciVisibilityTransport) send(p *payload) (body io.ReadCloser, err error) { + ciVisibilityPayload := &ciVisibilityPayload{p} + buffer, bufferErr := ciVisibilityPayload.getBuffer(t.config) + if bufferErr != nil { + return nil, fmt.Errorf("cannot create buffer payload: %v", bufferErr) + } + + if t.agentless { + // Compress payload + var gzipBuffer bytes.Buffer + gzipWriter := gzip.NewWriter(&gzipBuffer) + _, err = io.Copy(gzipWriter, buffer) + if err != nil { + return nil, fmt.Errorf("cannot compress request body: %v", err) + } + err = gzipWriter.Close() + if err != nil { + return nil, fmt.Errorf("cannot compress request body: %v", err) + } + buffer = &gzipBuffer + } + + req, err := http.NewRequest("POST", t.testCycleURLPath, buffer) + if err != nil { + return nil, fmt.Errorf("cannot create http request: %v", err) + } + for header, value := range t.headers { + req.Header.Set(header, value) + } + req.Header.Set("Content-Length", strconv.Itoa(buffer.Len())) + if t.agentless { + req.Header.Set("Content-Encoding", "gzip") + } + + response, err := t.config.httpClient.Do(req) + if err != nil { + return nil, err + } + if code := response.StatusCode; code >= 400 { + // error, check the body for context information and + // return a nice error. + msg := make([]byte, 1000) + n, _ := response.Body.Read(msg) + _ = response.Body.Close() + txt := http.StatusText(code) + if n > 0 { + return nil, fmt.Errorf("%s (Status: %s)", msg[:n], txt) + } + return nil, fmt.Errorf("%s", txt) + } + return response.Body, nil +} + +// sendStats is a no-op for CI Visibility transport as it does not support sending stats payloads. +// +// Parameters: +// +// payload - The stats payload to be sent. +// +// Returns: +// +// An error indicating that stats are not supported. +func (t *ciVisibilityTransport) sendStats(*statsPayload) error { + // Stats are not supported by CI Visibility agentless / EVP proxy. + return nil +} + +// endpoint returns the URL path of the test cycle endpoint. +// +// Returns: +// +// The URL path as a string. +func (t *ciVisibilityTransport) endpoint() string { + return t.testCycleURLPath +} diff --git a/ddtrace/tracer/civisibility_transport_test.go b/ddtrace/tracer/civisibility_transport_test.go new file mode 100644 index 0000000000..72240f2c7e --- /dev/null +++ b/ddtrace/tracer/civisibility_transport_test.go @@ -0,0 +1,112 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package tracer + +import ( + "bytes" + "compress/gzip" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tinylib/msgp/msgp" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" +) + +func TestCiVisibilityTransport(t *testing.T) { + t.Run("agentless", func(t *testing.T) { runTransportTest(t, true, true) }) + t.Run("agentless_no_api_key", func(t *testing.T) { runTransportTest(t, true, false) }) + t.Run("agentbased", func(t *testing.T) { runTransportTest(t, false, true) }) +} + +func runTransportTest(t *testing.T, agentless, shouldSetAPIKey bool) { + assert := assert.New(t) + + testCases := []struct { + payload [][]*span + }{ + {getTestTrace(1, 1)}, + {getTestTrace(10, 1)}, + {getTestTrace(100, 10)}, + } + + remainingEvents := 1000 + 10 + 1 + var hits int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + hits++ + metaLang := r.Header.Get("Datadog-Meta-Lang") + assert.NotNil(metaLang) + + if agentless && shouldSetAPIKey { + apikey := r.Header.Get("dd-api-key") + assert.Equal("12345", apikey) + } + + contentType := r.Header.Get("Content-Type") + assert.Equal("application/msgpack", contentType) + + assert.True(strings.HasSuffix(r.RequestURI, TestCyclePath)) + + bodyBuffer := new(bytes.Buffer) + if r.Header.Get("Content-Encoding") == "gzip" { + gzipReader, err := gzip.NewReader(r.Body) + assert.NoError(err) + + _, err = bodyBuffer.ReadFrom(gzipReader) + assert.NoError(err) + } else { + _, err := bodyBuffer.ReadFrom(r.Body) + assert.NoError(err) + } + + var testCyclePayload ciTestCyclePayload + err := msgp.Decode(bodyBuffer, &testCyclePayload) + assert.NoError(err) + + var events ciVisibilityEvents + err = msgp.Decode(bytes.NewBuffer(testCyclePayload.Events), &events) + assert.NoError(err) + + remainingEvents = remainingEvents - len(events) + })) + defer srv.Close() + + parsedURL, _ := url.Parse(srv.URL) + c := config{ + ciVisibilityEnabled: true, + httpClient: defaultHTTPClient(0), + agentURL: parsedURL, + } + + // Set CI Visibility environment variables for the test + if agentless { + t.Setenv(constants.CIVisibilityAgentlessEnabledEnvironmentVariable, "1") + t.Setenv(constants.CIVisibilityAgentlessURLEnvironmentVariable, srv.URL) + if shouldSetAPIKey { + t.Setenv(constants.APIKeyEnvironmentVariable, "12345") + } + } + + for _, tc := range testCases { + transport := newCiVisibilityTransport(&c) + + p := newCiVisibilityPayload() + for _, t := range tc.payload { + for _, span := range t { + err := p.push(getCiVisibilityEvent(span)) + assert.NoError(err) + } + } + + _, err := transport.send(p.payload) + assert.NoError(err) + } + assert.Equal(hits, len(testCases)) + assert.Equal(remainingEvents, 0) +} diff --git a/ddtrace/tracer/civisibility_tslv.go b/ddtrace/tracer/civisibility_tslv.go new file mode 100644 index 0000000000..377f6d5656 --- /dev/null +++ b/ddtrace/tracer/civisibility_tslv.go @@ -0,0 +1,441 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +//go:generate msgp -unexported -marshal=false -o=civisibility_tslv_msgp.go -tests=false + +package tracer + +import ( + "strconv" + + "github.com/tinylib/msgp/msgp" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" +) + +type ( + // ciTestCyclePayloadList implements msgp.Decodable on top of a slice of ciVisibilityPayloads. + // This type is only used in tests. + ciTestCyclePayloadList []*ciTestCyclePayload + + // ciVisibilityEvents is a slice of ciVisibilityEvent pointers. + ciVisibilityEvents []*ciVisibilityEvent +) + +// Ensure that ciVisibilityEvent and related types implement necessary interfaces. +var ( + _ ddtrace.Span = (*ciVisibilityEvent)(nil) + _ msgp.Encodable = (*ciVisibilityEvent)(nil) + _ msgp.Decodable = (*ciVisibilityEvent)(nil) + + _ msgp.Encodable = (*ciVisibilityEvents)(nil) + _ msgp.Decodable = (*ciVisibilityEvents)(nil) + + _ msgp.Encodable = (*ciTestCyclePayload)(nil) + _ msgp.Decodable = (*ciTestCyclePayloadList)(nil) +) + +// ciTestCyclePayload represents the payload for CI test cycles, including version, metadata, and events. +type ciTestCyclePayload struct { + Version int32 `msg:"version"` // Version of the payload format + Metadata map[string]map[string]string `msg:"metadata"` // Metadata associated with the payload + Events msgp.Raw `msg:"events"` // Encoded events data +} + +// ciVisibilityEvent represents a CI visibility event, including type, version, and content. +// It implements the ddtrace.Span interface. +// According to the CI Visibility event specification it has the following format for tests: +// +// { +// "type": "test", +// "version": 2, +// "content": { +// "type": "test", +// "trace_id": 123456, +// "span_id": 654321, +// "parent_id": 0, +// "test_session_id": 123456789, +// "test_module_id": 234567890, +// "test_suite_id": 123123123, +// "name": "...", +// "resource": "...", +// "error": 0, +// "meta": { +// ... +// }, +// "metrics": { +// ... +// }, +// "start": 1654698415668011500, +// "duration": 796143, +// "service": "..." +// } +// } +// +// For test suites: +// +// { +// "type": "test_suite_end", +// "version": 1, +// "content": { +// "type": "test_suite_end", +// "test_module_id": 234567890, +// "test_session_id": 123456789, +// "test_suite_id": 123123123, +// "name": "...", +// "resource": "...", +// "error": 0, +// "meta": { +// ... +// }, +// "metrics": { +// ... +// }, +// "start": 1654698415668011500, +// "duration": 796143, +// "service": "..." +// } +// } +// +// For test modules: +// +// { +// "type": "test_module_end", +// "version": 1, +// "content": { +// "type": "test_module_end", +// "test_session_id": 123456789, +// "test_module_id": 234567890, +// "error": 0, +// "name": "...", +// "resource": "...", +// "meta": { +// ... +// }, +// "metrics": { +// ... +// }, +// "start": 1654698415668011500, +// "duration": 796143, +// "service": "..." +// } +// } +// +// For test sessions: +// +// { +// "type": "test_session_end", +// "version": 1, +// "content": { +// "type": "test_session_end", +// "test_session_id": 123456789, +// "name": "...", +// "resource": "...", +// "error": 0, +// "meta": { +// ... +// }, +// "metrics": { +// ... +// }, +// "start": 1654698415668011500, +// "duration": 796143, +// "service": "..." +// } +// } +// +// A complete specification for the meta and metrics maps for each type can be found at: https://github.com/DataDog/datadog-ci-spec/tree/main/spec/citest +type ciVisibilityEvent struct { + Type string `msg:"type"` // Type of the CI visibility event + Version int32 `msg:"version"` // Version of the event type + Content tslvSpan `msg:"content"` // Content of the event + + span *span `msg:"-"` // Associated span (not marshaled) +} + +// SetTag sets a tag on the event's span and updates the content metadata and metrics. +// +// Parameters: +// +// key - The tag key. +// value - The tag value. +func (e *ciVisibilityEvent) SetTag(key string, value interface{}) { + e.span.SetTag(key, value) + e.Content.Meta = e.span.Meta + e.Content.Metrics = e.span.Metrics +} + +// SetOperationName sets the operation name of the event's span and updates the content name. +// +// Parameters: +// +// operationName - The new operation name. +func (e *ciVisibilityEvent) SetOperationName(operationName string) { + e.span.SetOperationName(operationName) + e.Content.Name = e.span.Name +} + +// BaggageItem retrieves the baggage item associated with the given key from the event's span. +// +// Parameters: +// +// key - The baggage item key. +// +// Returns: +// +// The baggage item value. +func (e *ciVisibilityEvent) BaggageItem(key string) string { + return e.span.BaggageItem(key) +} + +// SetBaggageItem sets a baggage item on the event's span. +// +// Parameters: +// +// key - The baggage item key. +// val - The baggage item value. +func (e *ciVisibilityEvent) SetBaggageItem(key, val string) { + e.span.SetBaggageItem(key, val) +} + +// Finish completes the event's span with optional finish options. +// +// Parameters: +// +// opts - Optional finish options. +func (e *ciVisibilityEvent) Finish(opts ...ddtrace.FinishOption) { + e.span.Finish(opts...) +} + +// Context returns the span context of the event's span. +// +// Returns: +// +// The span context. +func (e *ciVisibilityEvent) Context() ddtrace.SpanContext { + return e.span.Context() +} + +// tslvSpan represents the detailed information of a span for CI visibility. +type tslvSpan struct { + SessionID uint64 `msg:"test_session_id,omitempty"` // identifier of this session + ModuleID uint64 `msg:"test_module_id,omitempty"` // identifier of this module + SuiteID uint64 `msg:"test_suite_id,omitempty"` // identifier of this suite + CorrelationID string `msg:"itr_correlation_id,omitempty"` // Correlation Id for Intelligent Test Runner transactions + Name string `msg:"name"` // operation name + Service string `msg:"service"` // service name (i.e. "grpc.server", "http.request") + Resource string `msg:"resource"` // resource name (i.e. "/user?id=123", "SELECT * FROM users") + Type string `msg:"type"` // protocol associated with the span (i.e. "web", "db", "cache") + Start int64 `msg:"start"` // span start time expressed in nanoseconds since epoch + Duration int64 `msg:"duration"` // duration of the span expressed in nanoseconds + SpanID uint64 `msg:"span_id,omitempty"` // identifier of this span + TraceID uint64 `msg:"trace_id,omitempty"` // lower 64-bits of the root span identifier + ParentID uint64 `msg:"parent_id,omitempty"` // identifier of the span's direct parent + Error int32 `msg:"error"` // error status of the span; 0 means no errors + Meta map[string]string `msg:"meta,omitempty"` // arbitrary map of metadata + Metrics map[string]float64 `msg:"metrics,omitempty"` // arbitrary map of numeric metrics +} + +// getCiVisibilityEvent creates a ciVisibilityEvent from a span based on the span type. +// +// Parameters: +// +// span - The span to convert into a ciVisibilityEvent. +// +// Returns: +// +// A pointer to the created ciVisibilityEvent. +func getCiVisibilityEvent(span *span) *ciVisibilityEvent { + switch span.Type { + case constants.SpanTypeTest: + return createTestEventFromSpan(span) + case constants.SpanTypeTestSuite: + return createTestSuiteEventFromSpan(span) + case constants.SpanTypeTestModule: + return createTestModuleEventFromSpan(span) + case constants.SpanTypeTestSession: + return createTestSessionEventFromSpan(span) + default: + return createSpanEventFromSpan(span) + } +} + +// createTestEventFromSpan creates a ciVisibilityEvent of type Test from a span. +// +// Parameters: +// +// span - The span to convert. +// +// Returns: +// +// A pointer to the created ciVisibilityEvent. +func createTestEventFromSpan(span *span) *ciVisibilityEvent { + tSpan := createTslvSpan(span) + tSpan.SessionID = getAndRemoveMetaToUInt64(span, constants.TestSessionIDTag) + tSpan.ModuleID = getAndRemoveMetaToUInt64(span, constants.TestModuleIDTag) + tSpan.SuiteID = getAndRemoveMetaToUInt64(span, constants.TestSuiteIDTag) + tSpan.CorrelationID = getAndRemoveMeta(span, constants.ItrCorrelationIDTag) + tSpan.SpanID = span.SpanID + tSpan.TraceID = span.TraceID + return &ciVisibilityEvent{ + span: span, + Type: constants.SpanTypeTest, + Version: 2, + Content: tSpan, + } +} + +// createTestSuiteEventFromSpan creates a ciVisibilityEvent of type TestSuite from a span. +// +// Parameters: +// +// span - The span to convert. +// +// Returns: +// +// A pointer to the created ciVisibilityEvent. +func createTestSuiteEventFromSpan(span *span) *ciVisibilityEvent { + tSpan := createTslvSpan(span) + tSpan.SessionID = getAndRemoveMetaToUInt64(span, constants.TestSessionIDTag) + tSpan.ModuleID = getAndRemoveMetaToUInt64(span, constants.TestModuleIDTag) + tSpan.SuiteID = getAndRemoveMetaToUInt64(span, constants.TestSuiteIDTag) + return &ciVisibilityEvent{ + span: span, + Type: constants.SpanTypeTestSuite, + Version: 1, + Content: tSpan, + } +} + +// createTestModuleEventFromSpan creates a ciVisibilityEvent of type TestModule from a span. +// +// Parameters: +// +// span - The span to convert. +// +// Returns: +// +// A pointer to the created ciVisibilityEvent. +func createTestModuleEventFromSpan(span *span) *ciVisibilityEvent { + tSpan := createTslvSpan(span) + tSpan.SessionID = getAndRemoveMetaToUInt64(span, constants.TestSessionIDTag) + tSpan.ModuleID = getAndRemoveMetaToUInt64(span, constants.TestModuleIDTag) + return &ciVisibilityEvent{ + span: span, + Type: constants.SpanTypeTestModule, + Version: 1, + Content: tSpan, + } +} + +// createTestSessionEventFromSpan creates a ciVisibilityEvent of type TestSession from a span. +// +// Parameters: +// +// span - The span to convert. +// +// Returns: +// +// A pointer to the created ciVisibilityEvent. +func createTestSessionEventFromSpan(span *span) *ciVisibilityEvent { + tSpan := createTslvSpan(span) + tSpan.SessionID = getAndRemoveMetaToUInt64(span, constants.TestSessionIDTag) + return &ciVisibilityEvent{ + span: span, + Type: constants.SpanTypeTestSession, + Version: 1, + Content: tSpan, + } +} + +// createSpanEventFromSpan creates a ciVisibilityEvent of type Span from a span. +// +// Parameters: +// +// span - The span to convert. +// +// Returns: +// +// A pointer to the created ciVisibilityEvent. +func createSpanEventFromSpan(span *span) *ciVisibilityEvent { + tSpan := createTslvSpan(span) + tSpan.SpanID = span.SpanID + tSpan.TraceID = span.TraceID + return &ciVisibilityEvent{ + span: span, + Type: constants.SpanTypeSpan, + Version: 1, + Content: tSpan, + } +} + +// createTslvSpan creates a tslvSpan from a span. +// +// Parameters: +// +// span - The span to convert. +// +// Returns: +// +// The created tslvSpan. +func createTslvSpan(span *span) tslvSpan { + return tslvSpan{ + Name: span.Name, + Service: span.Service, + Resource: span.Resource, + Type: span.Type, + Start: span.Start, + Duration: span.Duration, + ParentID: span.ParentID, + Error: span.Error, + Meta: span.Meta, + Metrics: span.Metrics, + } +} + +// getAndRemoveMeta retrieves a metadata value from a span and removes it from the span's metadata and metrics. +// +// Parameters: +// +// span - The span to modify. +// key - The metadata key to retrieve and remove. +// +// Returns: +// +// The retrieved metadata value. +func getAndRemoveMeta(span *span, key string) string { + span.Lock() + defer span.Unlock() + if span.Meta == nil { + span.Meta = make(map[string]string, 1) + } + + if v, ok := span.Meta[key]; ok { + delete(span.Meta, key) + delete(span.Metrics, key) + return v + } + + return "" +} + +// getAndRemoveMetaToUInt64 retrieves a metadata value from a span, removes it, and converts it to a uint64. +// +// Parameters: +// +// span - The span to modify. +// key - The metadata key to retrieve and convert. +// +// Returns: +// +// The retrieved and converted metadata value as a uint64. +func getAndRemoveMetaToUInt64(span *span, key string) uint64 { + strValue := getAndRemoveMeta(span, key) + i, err := strconv.ParseUint(strValue, 10, 64) + if err != nil { + return 0 + } + return i +} diff --git a/ddtrace/tracer/civisibility_tslv_msgp.go b/ddtrace/tracer/civisibility_tslv_msgp.go new file mode 100644 index 0000000000..63fa4b8499 --- /dev/null +++ b/ddtrace/tracer/civisibility_tslv_msgp.go @@ -0,0 +1,924 @@ +package tracer + +// Code generated by github.com/tinylib/msgp DO NOT EDIT. + +import ( + "github.com/tinylib/msgp/msgp" +) + +// DecodeMsg implements msgp.Decodable +func (z *ciTestCyclePayload) DecodeMsg(dc *msgp.Reader) (err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "version": + z.Version, err = dc.ReadInt32() + if err != nil { + err = msgp.WrapError(err, "Version") + return + } + case "metadata": + var zb0002 uint32 + zb0002, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err, "Metadata") + return + } + if z.Metadata == nil { + z.Metadata = make(map[string]map[string]string, zb0002) + } else if len(z.Metadata) > 0 { + for key := range z.Metadata { + delete(z.Metadata, key) + } + } + for zb0002 > 0 { + zb0002-- + var za0001 string + var za0002 map[string]string + za0001, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Metadata") + return + } + var zb0003 uint32 + zb0003, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err, "Metadata", za0001) + return + } + if za0002 == nil { + za0002 = make(map[string]string, zb0003) + } else if len(za0002) > 0 { + for key := range za0002 { + delete(za0002, key) + } + } + for zb0003 > 0 { + zb0003-- + var za0003 string + var za0004 string + za0003, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Metadata", za0001) + return + } + za0004, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Metadata", za0001, za0003) + return + } + za0002[za0003] = za0004 + } + z.Metadata[za0001] = za0002 + } + case "events": + err = z.Events.DecodeMsg(dc) + if err != nil { + err = msgp.WrapError(err, "Events") + return + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z *ciTestCyclePayload) EncodeMsg(en *msgp.Writer) (err error) { + // map header, size 3 + // write "version" + err = en.Append(0x83, 0xa7, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e) + if err != nil { + return + } + err = en.WriteInt32(z.Version) + if err != nil { + err = msgp.WrapError(err, "Version") + return + } + // write "metadata" + err = en.Append(0xa8, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61) + if err != nil { + return + } + err = en.WriteMapHeader(uint32(len(z.Metadata))) + if err != nil { + err = msgp.WrapError(err, "Metadata") + return + } + for za0001, za0002 := range z.Metadata { + err = en.WriteString(za0001) + if err != nil { + err = msgp.WrapError(err, "Metadata") + return + } + err = en.WriteMapHeader(uint32(len(za0002))) + if err != nil { + err = msgp.WrapError(err, "Metadata", za0001) + return + } + for za0003, za0004 := range za0002 { + err = en.WriteString(za0003) + if err != nil { + err = msgp.WrapError(err, "Metadata", za0001) + return + } + err = en.WriteString(za0004) + if err != nil { + err = msgp.WrapError(err, "Metadata", za0001, za0003) + return + } + } + } + // write "events" + err = en.Append(0xa6, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73) + if err != nil { + return + } + err = z.Events.EncodeMsg(en) + if err != nil { + err = msgp.WrapError(err, "Events") + return + } + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *ciTestCyclePayload) Msgsize() (s int) { + s = 1 + 8 + msgp.Int32Size + 9 + msgp.MapHeaderSize + if z.Metadata != nil { + for za0001, za0002 := range z.Metadata { + _ = za0002 + s += msgp.StringPrefixSize + len(za0001) + msgp.MapHeaderSize + if za0002 != nil { + for za0003, za0004 := range za0002 { + _ = za0004 + s += msgp.StringPrefixSize + len(za0003) + msgp.StringPrefixSize + len(za0004) + } + } + } + } + s += 7 + z.Events.Msgsize() + return +} + +// DecodeMsg implements msgp.Decodable +func (z *ciTestCyclePayloadList) DecodeMsg(dc *msgp.Reader) (err error) { + var zb0002 uint32 + zb0002, err = dc.ReadArrayHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + if cap((*z)) >= int(zb0002) { + (*z) = (*z)[:zb0002] + } else { + (*z) = make(ciTestCyclePayloadList, zb0002) + } + for zb0001 := range *z { + if dc.IsNil() { + err = dc.ReadNil() + if err != nil { + err = msgp.WrapError(err, zb0001) + return + } + (*z)[zb0001] = nil + } else { + if (*z)[zb0001] == nil { + (*z)[zb0001] = new(ciTestCyclePayload) + } + err = (*z)[zb0001].DecodeMsg(dc) + if err != nil { + err = msgp.WrapError(err, zb0001) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z ciTestCyclePayloadList) EncodeMsg(en *msgp.Writer) (err error) { + err = en.WriteArrayHeader(uint32(len(z))) + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0003 := range z { + if z[zb0003] == nil { + err = en.WriteNil() + if err != nil { + return + } + } else { + err = z[zb0003].EncodeMsg(en) + if err != nil { + err = msgp.WrapError(err, zb0003) + return + } + } + } + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z ciTestCyclePayloadList) Msgsize() (s int) { + s = msgp.ArrayHeaderSize + for zb0003 := range z { + if z[zb0003] == nil { + s += msgp.NilSize + } else { + s += z[zb0003].Msgsize() + } + } + return +} + +// DecodeMsg implements msgp.Decodable +func (z *ciVisibilityEvent) DecodeMsg(dc *msgp.Reader) (err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "type": + z.Type, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Type") + return + } + case "version": + z.Version, err = dc.ReadInt32() + if err != nil { + err = msgp.WrapError(err, "Version") + return + } + case "content": + err = z.Content.DecodeMsg(dc) + if err != nil { + err = msgp.WrapError(err, "Content") + return + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z *ciVisibilityEvent) EncodeMsg(en *msgp.Writer) (err error) { + // map header, size 3 + // write "type" + err = en.Append(0x83, 0xa4, 0x74, 0x79, 0x70, 0x65) + if err != nil { + return + } + err = en.WriteString(z.Type) + if err != nil { + err = msgp.WrapError(err, "Type") + return + } + // write "version" + err = en.Append(0xa7, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e) + if err != nil { + return + } + err = en.WriteInt32(z.Version) + if err != nil { + err = msgp.WrapError(err, "Version") + return + } + // write "content" + err = en.Append(0xa7, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74) + if err != nil { + return + } + err = z.Content.EncodeMsg(en) + if err != nil { + err = msgp.WrapError(err, "Content") + return + } + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *ciVisibilityEvent) Msgsize() (s int) { + s = 1 + 5 + msgp.StringPrefixSize + len(z.Type) + 8 + msgp.Int32Size + 8 + z.Content.Msgsize() + return +} + +// DecodeMsg implements msgp.Decodable +func (z *ciVisibilityEvents) DecodeMsg(dc *msgp.Reader) (err error) { + var zb0002 uint32 + zb0002, err = dc.ReadArrayHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + if cap((*z)) >= int(zb0002) { + (*z) = (*z)[:zb0002] + } else { + (*z) = make(ciVisibilityEvents, zb0002) + } + for zb0001 := range *z { + if dc.IsNil() { + err = dc.ReadNil() + if err != nil { + err = msgp.WrapError(err, zb0001) + return + } + (*z)[zb0001] = nil + } else { + if (*z)[zb0001] == nil { + (*z)[zb0001] = new(ciVisibilityEvent) + } + var field []byte + _ = field + var zb0003 uint32 + zb0003, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err, zb0001) + return + } + for zb0003 > 0 { + zb0003-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err, zb0001) + return + } + switch msgp.UnsafeString(field) { + case "type": + (*z)[zb0001].Type, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, zb0001, "Type") + return + } + case "version": + (*z)[zb0001].Version, err = dc.ReadInt32() + if err != nil { + err = msgp.WrapError(err, zb0001, "Version") + return + } + case "content": + err = (*z)[zb0001].Content.DecodeMsg(dc) + if err != nil { + err = msgp.WrapError(err, zb0001, "Content") + return + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err, zb0001) + return + } + } + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z ciVisibilityEvents) EncodeMsg(en *msgp.Writer) (err error) { + err = en.WriteArrayHeader(uint32(len(z))) + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0004 := range z { + if z[zb0004] == nil { + err = en.WriteNil() + if err != nil { + return + } + } else { + // map header, size 3 + // write "type" + err = en.Append(0x83, 0xa4, 0x74, 0x79, 0x70, 0x65) + if err != nil { + return + } + err = en.WriteString(z[zb0004].Type) + if err != nil { + err = msgp.WrapError(err, zb0004, "Type") + return + } + // write "version" + err = en.Append(0xa7, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e) + if err != nil { + return + } + err = en.WriteInt32(z[zb0004].Version) + if err != nil { + err = msgp.WrapError(err, zb0004, "Version") + return + } + // write "content" + err = en.Append(0xa7, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74) + if err != nil { + return + } + err = z[zb0004].Content.EncodeMsg(en) + if err != nil { + err = msgp.WrapError(err, zb0004, "Content") + return + } + } + } + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z ciVisibilityEvents) Msgsize() (s int) { + s = msgp.ArrayHeaderSize + for zb0004 := range z { + if z[zb0004] == nil { + s += msgp.NilSize + } else { + s += 1 + 5 + msgp.StringPrefixSize + len(z[zb0004].Type) + 8 + msgp.Int32Size + 8 + z[zb0004].Content.Msgsize() + } + } + return +} + +// DecodeMsg implements msgp.Decodable +func (z *tslvSpan) DecodeMsg(dc *msgp.Reader) (err error) { + var field []byte + _ = field + var zb0001 uint32 + zb0001, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err) + return + } + for zb0001 > 0 { + zb0001-- + field, err = dc.ReadMapKeyPtr() + if err != nil { + err = msgp.WrapError(err) + return + } + switch msgp.UnsafeString(field) { + case "test_session_id": + z.SessionID, err = dc.ReadUint64() + if err != nil { + err = msgp.WrapError(err, "SessionId") + return + } + case "test_module_id": + z.ModuleID, err = dc.ReadUint64() + if err != nil { + err = msgp.WrapError(err, "ModuleId") + return + } + case "test_suite_id": + z.SuiteID, err = dc.ReadUint64() + if err != nil { + err = msgp.WrapError(err, "SuiteId") + return + } + case "itr_correlation_id": + z.CorrelationID, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "CorrelationId") + return + } + case "name": + z.Name, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Name") + return + } + case "service": + z.Service, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Service") + return + } + case "resource": + z.Resource, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Resource") + return + } + case "type": + z.Type, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Type") + return + } + case "start": + z.Start, err = dc.ReadInt64() + if err != nil { + err = msgp.WrapError(err, "Start") + return + } + case "duration": + z.Duration, err = dc.ReadInt64() + if err != nil { + err = msgp.WrapError(err, "Duration") + return + } + case "span_id": + z.SpanID, err = dc.ReadUint64() + if err != nil { + err = msgp.WrapError(err, "SpanID") + return + } + case "trace_id": + z.TraceID, err = dc.ReadUint64() + if err != nil { + err = msgp.WrapError(err, "TraceID") + return + } + case "parent_id": + z.ParentID, err = dc.ReadUint64() + if err != nil { + err = msgp.WrapError(err, "ParentID") + return + } + case "error": + z.Error, err = dc.ReadInt32() + if err != nil { + err = msgp.WrapError(err, "Error") + return + } + case "meta": + var zb0002 uint32 + zb0002, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err, "Meta") + return + } + if z.Meta == nil { + z.Meta = make(map[string]string, zb0002) + } else if len(z.Meta) > 0 { + for key := range z.Meta { + delete(z.Meta, key) + } + } + for zb0002 > 0 { + zb0002-- + var za0001 string + var za0002 string + za0001, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Meta") + return + } + za0002, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Meta", za0001) + return + } + z.Meta[za0001] = za0002 + } + case "metrics": + var zb0003 uint32 + zb0003, err = dc.ReadMapHeader() + if err != nil { + err = msgp.WrapError(err, "Metrics") + return + } + if z.Metrics == nil { + z.Metrics = make(map[string]float64, zb0003) + } else if len(z.Metrics) > 0 { + for key := range z.Metrics { + delete(z.Metrics, key) + } + } + for zb0003 > 0 { + zb0003-- + var za0003 string + var za0004 float64 + za0003, err = dc.ReadString() + if err != nil { + err = msgp.WrapError(err, "Metrics") + return + } + za0004, err = dc.ReadFloat64() + if err != nil { + err = msgp.WrapError(err, "Metrics", za0003) + return + } + z.Metrics[za0003] = za0004 + } + default: + err = dc.Skip() + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + return +} + +// EncodeMsg implements msgp.Encodable +func (z *tslvSpan) EncodeMsg(en *msgp.Writer) (err error) { + // omitempty: check for empty values + zb0001Len := uint32(16) + var zb0001Mask uint16 /* 16 bits */ + _ = zb0001Mask + if z.SessionID == 0 { + zb0001Len-- + zb0001Mask |= 0x1 + } + if z.ModuleID == 0 { + zb0001Len-- + zb0001Mask |= 0x2 + } + if z.SuiteID == 0 { + zb0001Len-- + zb0001Mask |= 0x4 + } + if z.CorrelationID == "" { + zb0001Len-- + zb0001Mask |= 0x8 + } + if z.SpanID == 0 { + zb0001Len-- + zb0001Mask |= 0x400 + } + if z.TraceID == 0 { + zb0001Len-- + zb0001Mask |= 0x800 + } + if z.ParentID == 0 { + zb0001Len-- + zb0001Mask |= 0x1000 + } + if z.Meta == nil { + zb0001Len-- + zb0001Mask |= 0x4000 + } + if z.Metrics == nil { + zb0001Len-- + zb0001Mask |= 0x8000 + } + // variable map header, size zb0001Len + err = en.WriteMapHeader(zb0001Len) + if err != nil { + return + } + if zb0001Len == 0 { + return + } + if (zb0001Mask & 0x1) == 0 { // if not empty + // write "test_session_id" + err = en.Append(0xaf, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64) + if err != nil { + return + } + err = en.WriteUint64(z.SessionID) + if err != nil { + err = msgp.WrapError(err, "SessionID") + return + } + } + if (zb0001Mask & 0x2) == 0 { // if not empty + // write "test_module_id" + err = en.Append(0xae, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64) + if err != nil { + return + } + err = en.WriteUint64(z.ModuleID) + if err != nil { + err = msgp.WrapError(err, "ModuleID") + return + } + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // write "test_suite_id" + err = en.Append(0xad, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x64) + if err != nil { + return + } + err = en.WriteUint64(z.SuiteID) + if err != nil { + err = msgp.WrapError(err, "SuiteID") + return + } + } + if (zb0001Mask & 0x8) == 0 { // if not empty + // write "itr_correlation_id" + err = en.Append(0xb2, 0x69, 0x74, 0x72, 0x5f, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64) + if err != nil { + return + } + err = en.WriteString(z.CorrelationID) + if err != nil { + err = msgp.WrapError(err, "CorrelationID") + return + } + } + // write "name" + err = en.Append(0xa4, 0x6e, 0x61, 0x6d, 0x65) + if err != nil { + return + } + err = en.WriteString(z.Name) + if err != nil { + err = msgp.WrapError(err, "Name") + return + } + // write "service" + err = en.Append(0xa7, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65) + if err != nil { + return + } + err = en.WriteString(z.Service) + if err != nil { + err = msgp.WrapError(err, "Service") + return + } + // write "resource" + err = en.Append(0xa8, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65) + if err != nil { + return + } + err = en.WriteString(z.Resource) + if err != nil { + err = msgp.WrapError(err, "Resource") + return + } + // write "type" + err = en.Append(0xa4, 0x74, 0x79, 0x70, 0x65) + if err != nil { + return + } + err = en.WriteString(z.Type) + if err != nil { + err = msgp.WrapError(err, "Type") + return + } + // write "start" + err = en.Append(0xa5, 0x73, 0x74, 0x61, 0x72, 0x74) + if err != nil { + return + } + err = en.WriteInt64(z.Start) + if err != nil { + err = msgp.WrapError(err, "Start") + return + } + // write "duration" + err = en.Append(0xa8, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e) + if err != nil { + return + } + err = en.WriteInt64(z.Duration) + if err != nil { + err = msgp.WrapError(err, "Duration") + return + } + if (zb0001Mask & 0x400) == 0 { // if not empty + // write "span_id" + err = en.Append(0xa7, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x69, 0x64) + if err != nil { + return + } + err = en.WriteUint64(z.SpanID) + if err != nil { + err = msgp.WrapError(err, "SpanID") + return + } + } + if (zb0001Mask & 0x800) == 0 { // if not empty + // write "trace_id" + err = en.Append(0xa8, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64) + if err != nil { + return + } + err = en.WriteUint64(z.TraceID) + if err != nil { + err = msgp.WrapError(err, "TraceID") + return + } + } + if (zb0001Mask & 0x1000) == 0 { // if not empty + // write "parent_id" + err = en.Append(0xa9, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64) + if err != nil { + return + } + err = en.WriteUint64(z.ParentID) + if err != nil { + err = msgp.WrapError(err, "ParentID") + return + } + } + // write "error" + err = en.Append(0xa5, 0x65, 0x72, 0x72, 0x6f, 0x72) + if err != nil { + return + } + err = en.WriteInt32(z.Error) + if err != nil { + err = msgp.WrapError(err, "Error") + return + } + if (zb0001Mask & 0x4000) == 0 { // if not empty + // write "meta" + err = en.Append(0xa4, 0x6d, 0x65, 0x74, 0x61) + if err != nil { + return + } + err = en.WriteMapHeader(uint32(len(z.Meta))) + if err != nil { + err = msgp.WrapError(err, "Meta") + return + } + for za0001, za0002 := range z.Meta { + err = en.WriteString(za0001) + if err != nil { + err = msgp.WrapError(err, "Meta") + return + } + err = en.WriteString(za0002) + if err != nil { + err = msgp.WrapError(err, "Meta", za0001) + return + } + } + } + if (zb0001Mask & 0x8000) == 0 { // if not empty + // write "metrics" + err = en.Append(0xa7, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73) + if err != nil { + return + } + err = en.WriteMapHeader(uint32(len(z.Metrics))) + if err != nil { + err = msgp.WrapError(err, "Metrics") + return + } + for za0003, za0004 := range z.Metrics { + err = en.WriteString(za0003) + if err != nil { + err = msgp.WrapError(err, "Metrics") + return + } + err = en.WriteFloat64(za0004) + if err != nil { + err = msgp.WrapError(err, "Metrics", za0003) + return + } + } + } + return +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *tslvSpan) Msgsize() (s int) { + s = 3 + 16 + msgp.Uint64Size + 15 + msgp.Uint64Size + 14 + msgp.Uint64Size + 19 + msgp.StringPrefixSize + len(z.CorrelationID) + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.StringPrefixSize + len(z.Service) + 9 + msgp.StringPrefixSize + len(z.Resource) + 5 + msgp.StringPrefixSize + len(z.Type) + 6 + msgp.Int64Size + 9 + msgp.Int64Size + 8 + msgp.Uint64Size + 9 + msgp.Uint64Size + 10 + msgp.Uint64Size + 6 + msgp.Int32Size + 5 + msgp.MapHeaderSize + if z.Meta != nil { + for za0001, za0002 := range z.Meta { + _ = za0002 + s += msgp.StringPrefixSize + len(za0001) + msgp.StringPrefixSize + len(za0002) + } + } + s += 8 + msgp.MapHeaderSize + if z.Metrics != nil { + for za0003, za0004 := range z.Metrics { + _ = za0004 + s += msgp.StringPrefixSize + len(za0003) + msgp.Float64Size + } + } + return +} diff --git a/ddtrace/tracer/civisibility_writer.go b/ddtrace/tracer/civisibility_writer.go new file mode 100644 index 0000000000..1582b200a8 --- /dev/null +++ b/ddtrace/tracer/civisibility_writer.go @@ -0,0 +1,119 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package tracer + +import ( + "sync" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) + +// Constants defining the payload size limits for agentless mode. +const ( + // agentlessPayloadMaxLimit is the maximum payload size allowed, indicating the + // maximum size of the package that the intake can receive. + agentlessPayloadMaxLimit = 5 * 1024 * 1024 // 5 MB + + // agentlessPayloadSizeLimit specifies the maximum allowed size of the payload before + // it triggers a flush to the transport. + agentlessPayloadSizeLimit = agentlessPayloadMaxLimit / 2 +) + +// Ensure that ciVisibilityTraceWriter implements the traceWriter interface. +var _ traceWriter = (*ciVisibilityTraceWriter)(nil) + +// ciVisibilityTraceWriter is responsible for buffering and sending CI visibility trace data +// to the Datadog backend. It manages the payload size and flushes the data when necessary. +type ciVisibilityTraceWriter struct { + config *config // Configuration for the tracer. + payload *ciVisibilityPayload // Encodes and buffers events in msgpack format. + climit chan struct{} // Limits the number of concurrent outgoing connections. + wg sync.WaitGroup // Waits for all uploads to finish. +} + +// newCiVisibilityTraceWriter creates a new instance of ciVisibilityTraceWriter. +// +// Parameters: +// +// c - The tracer configuration. +// +// Returns: +// +// A pointer to an initialized ciVisibilityTraceWriter. +func newCiVisibilityTraceWriter(c *config) *ciVisibilityTraceWriter { + return &ciVisibilityTraceWriter{ + config: c, + payload: newCiVisibilityPayload(), + climit: make(chan struct{}, concurrentConnectionLimit), + } +} + +// add adds a new trace to the payload. If the payload size exceeds the limit, +// it triggers a flush to send the data. +// +// Parameters: +// +// trace - A slice of spans representing the trace to be added. +func (w *ciVisibilityTraceWriter) add(trace []*span) { + for _, s := range trace { + cvEvent := getCiVisibilityEvent(s) + if err := w.payload.push(cvEvent); err != nil { + log.Error("Error encoding msgpack: %v", err) + } + if w.payload.size() > agentlessPayloadSizeLimit { + w.flush() + } + } +} + +// stop stops the trace writer, ensuring all data is flushed and all uploads are completed. +func (w *ciVisibilityTraceWriter) stop() { + w.flush() + w.wg.Wait() +} + +// flush sends the current payload to the transport. It ensures that the payload is reset +// and the resources are freed after the flush operation is completed. +func (w *ciVisibilityTraceWriter) flush() { + if w.payload.itemCount() == 0 { + return + } + + w.wg.Add(1) + w.climit <- struct{}{} + oldp := w.payload + w.payload = newCiVisibilityPayload() + + go func(p *ciVisibilityPayload) { + defer func(start time.Time) { + // Once the payload has been used, clear the buffer for garbage + // collection to avoid a memory leak when references to this object + // may still be kept by faulty transport implementations or the + // standard library. See dd-trace-go#976 + p.clear() + + <-w.climit + w.wg.Done() + }(time.Now()) + + var count, size int + var err error + for attempt := 0; attempt <= w.config.sendRetries; attempt++ { + size, count = p.size(), p.itemCount() + log.Debug("Sending payload: size: %d events: %d\n", size, count) + _, err = w.config.transport.send(p.payload) + if err == nil { + log.Debug("sent events after %d attempts", attempt+1) + return + } + log.Error("failure sending events (attempt %d), will retry: %v", attempt+1, err) + p.reset() + time.Sleep(time.Millisecond) + } + log.Error("lost %d events: %v", count, err) + }(oldp) +} diff --git a/ddtrace/tracer/civisibility_writer_test.go b/ddtrace/tracer/civisibility_writer_test.go new file mode 100644 index 0000000000..0757ec05bb --- /dev/null +++ b/ddtrace/tracer/civisibility_writer_test.go @@ -0,0 +1,101 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package tracer + +import ( + "errors" + "fmt" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tinylib/msgp/msgp" +) + +func TestCIVisibilityImplementsTraceWriter(t *testing.T) { + assert.Implements(t, (*traceWriter)(nil), &ciVisibilityTraceWriter{}) +} + +type failingCiVisibilityTransport struct { + dummyTransport + failCount int + sendAttempts int + tracesSent bool + events ciVisibilityEvents + assert *assert.Assertions +} + +func (t *failingCiVisibilityTransport) send(p *payload) (io.ReadCloser, error) { + t.sendAttempts++ + + ciVisibilityPayload := &ciVisibilityPayload{p} + + var events ciVisibilityEvents + err := msgp.Decode(ciVisibilityPayload, &events) + if err != nil { + return nil, err + } + if t.sendAttempts == 1 { + t.events = events + } else { + t.assert.Equal(t.events, events) + } + + if t.failCount > 0 { + t.failCount-- + return nil, errors.New("oops, I failed") + } + + t.tracesSent = true + return io.NopCloser(strings.NewReader("OK")), nil +} + +func TestCiVisibilityTraceWriterFlushRetries(t *testing.T) { + testcases := []struct { + configRetries int + failCount int + tracesSent bool + expAttempts int + }{ + {configRetries: 0, failCount: 0, tracesSent: true, expAttempts: 1}, + {configRetries: 0, failCount: 1, tracesSent: false, expAttempts: 1}, + + {configRetries: 1, failCount: 0, tracesSent: true, expAttempts: 1}, + {configRetries: 1, failCount: 1, tracesSent: true, expAttempts: 2}, + {configRetries: 1, failCount: 2, tracesSent: false, expAttempts: 2}, + + {configRetries: 2, failCount: 0, tracesSent: true, expAttempts: 1}, + {configRetries: 2, failCount: 1, tracesSent: true, expAttempts: 2}, + {configRetries: 2, failCount: 2, tracesSent: true, expAttempts: 3}, + {configRetries: 2, failCount: 3, tracesSent: false, expAttempts: 3}, + } + + ss := []*span{makeSpan(0)} + for _, test := range testcases { + name := fmt.Sprintf("%d-%d-%t-%d", test.configRetries, test.failCount, test.tracesSent, test.expAttempts) + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + p := &failingCiVisibilityTransport{ + failCount: test.failCount, + assert: assert, + } + c := newConfig(func(c *config) { + c.transport = p + c.sendRetries = test.configRetries + }) + + h := newCiVisibilityTraceWriter(c) + h.add(ss) + + h.flush() + h.wg.Wait() + + assert.Equal(test.expAttempts, p.sendAttempts) + assert.Equal(test.tracesSent, p.tracesSent) + }) + } +} diff --git a/ddtrace/tracer/context.go b/ddtrace/tracer/context.go index 5698dea685..ee29dca37d 100644 --- a/ddtrace/tracer/context.go +++ b/ddtrace/tracer/context.go @@ -11,11 +11,12 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" traceinternal "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/orchestrion" ) // ContextWithSpan returns a copy of the given context which includes the span s. func ContextWithSpan(ctx context.Context, s Span) context.Context { - return context.WithValue(ctx, internal.ActiveSpanKey, s) + return orchestrion.CtxWithValue(ctx, internal.ActiveSpanKey, s) } // SpanFromContext returns the span contained in the given context. A second return @@ -25,7 +26,7 @@ func SpanFromContext(ctx context.Context) (Span, bool) { if ctx == nil { return &traceinternal.NoopSpan{}, false } - v := ctx.Value(internal.ActiveSpanKey) + v := orchestrion.WrapContext(ctx).Value(internal.ActiveSpanKey) if s, ok := v.(ddtrace.Span); ok { return s, true } diff --git a/ddtrace/tracer/exec_tracer_test.go b/ddtrace/tracer/exec_tracer_test.go deleted file mode 100644 index 0ff8e239f9..0000000000 --- a/ddtrace/tracer/exec_tracer_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -// Tests in this file rely on parsing execution tracer data, which can change -// formats across Go releases. This guard should be updated as the Go trace -// parser library is upgraded to support new versions. -//go:build !go1.21 - -package tracer - -import ( - "bytes" - "context" - "encoding/binary" - rt "runtime/trace" - "testing" - - "github.com/stretchr/testify/assert" - gotraceui "honnef.co/go/gotraceui/trace" -) - -func TestExecutionTraceSpans(t *testing.T) { - if rt.IsEnabled() { - t.Skip("runtime execution tracing is already enabled") - } - - buf := new(bytes.Buffer) - if err := rt.Start(buf); err != nil { - t.Fatal(err) - } - // Ensure we unconditionally stop tracing. It's safe to call this - // multiple times. - defer rt.Stop() - - _, _, _, stop := startTestTracer(t) - defer stop() - - root, ctx := StartSpanFromContext(context.Background(), "root") - child, _ := StartSpanFromContext(ctx, "child") - root.Finish() - child.Finish() - - rt.Stop() - - execTrace, err := gotraceui.Parse(buf, nil) - if err != nil { - t.Fatalf("parsing trace: %s", err) - } - - type traceSpan struct { - name string - parent string - spanID uint64 - } - - spans := make(map[int]*traceSpan) - for _, ev := range execTrace.Events { - switch ev.Type { - case gotraceui.EvUserTaskCreate: - id := int(ev.Args[0]) - name := execTrace.Strings[ev.Args[2]] - var parent string - if p, ok := spans[int(ev.Args[1])]; ok { - parent = p.name - } - spans[id] = &traceSpan{ - name: name, - parent: parent, - } - case gotraceui.EvUserLog: - id := int(ev.Args[0]) - span, ok := spans[id] - if !ok { - continue - } - key := execTrace.Strings[ev.Args[1]] - if key == "datadog.uint64_span_id" { - span.spanID = binary.LittleEndian.Uint64([]byte(execTrace.Strings[ev.Args[3]])) - } - } - } - - want := []traceSpan{ - {name: "root", spanID: root.Context().SpanID()}, - {name: "child", parent: "root", spanID: child.Context().SpanID()}, - } - var got []traceSpan - for _, v := range spans { - got = append(got, *v) - } - - assert.ElementsMatch(t, want, got) -} diff --git a/ddtrace/tracer/log.go b/ddtrace/tracer/log.go index 77139f0f43..b1351edb34 100644 --- a/ddtrace/tracer/log.go +++ b/ddtrace/tracer/log.go @@ -57,6 +57,8 @@ type startupInfo struct { PartialFlushMinSpans int `json:"partial_flush_min_spans"` // The min number of spans to trigger a partial flush Orchestrion orchestrionConfig `json:"orchestrion"` // Orchestrion (auto-instrumentation) configuration. FeatureFlags []string `json:"feature_flags"` + PropagationStyleInject string `json:"propagation_style_inject"` // Propagation style for inject + PropagationStyleExtract string `json:"propagation_style_extract"` // Propagation style for extract } // checkEndpoint tries to connect to the URL specified by endpoint. @@ -90,6 +92,8 @@ func logStartup(t *tracer) { featureFlags = append(featureFlags, f) } + cp, _ := t.config.propagator.(*chainedPropagator) + info := startupInfo{ Date: time.Now().Format(time.RFC3339), OSName: osinfo.OSName(), @@ -123,6 +127,8 @@ func logStartup(t *tracer) { PartialFlushMinSpans: t.config.partialFlushMinSpans, Orchestrion: t.config.orchestrionCfg, FeatureFlags: featureFlags, + PropagationStyleInject: cp.injectorNames, + PropagationStyleExtract: cp.extractorsNames, } if _, _, err := samplingRulesFromEnv(); err != nil { info.SamplingRulesError = fmt.Sprintf("%s", err) diff --git a/ddtrace/tracer/log_test.go b/ddtrace/tracer/log_test.go index a26d902459..f91047c8bd 100644 --- a/ddtrace/tracer/log_test.go +++ b/ddtrace/tracer/log_test.go @@ -31,7 +31,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore("appsec: ", telemetry.LogPrefix) logStartup(tracer) require.Len(t, tp.Logs(), 2) - assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"disabled","trace_sampling_rules":null,"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":((true)|(false)),"Stats":((true)|(false)),"DataStreams":((true)|(false)),"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\]}`, tp.Logs()[1]) + assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"disabled","trace_sampling_rules":null,"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":((true)|(false)),"Stats":((true)|(false)),"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, tp.Logs()[1]) }) t.Run("configured", func(t *testing.T) { @@ -63,7 +63,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore("appsec: ", telemetry.LogPrefix) logStartup(tracer) require.Len(t, tp.Logs(), 2) - assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sample_rate_limit":"100","trace_sampling_rules":\[{"service":"mysql","sample_rate":0\.75}\],"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":{"initial_service":"new_service"},"tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"health_metrics_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"DataStreams":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":true,"metadata":{"version":"v1"}},"feature_flags":\["discovery"\]}`, tp.Logs()[1]) + assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sample_rate_limit":"100","trace_sampling_rules":\[{"service":"mysql","sample_rate":0\.75}\],"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":{"initial_service":"new_service"},"tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"health_metrics_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":true,"metadata":{"version":"v1"}},"feature_flags":\["discovery"\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, tp.Logs()[1]) }) t.Run("limit", func(t *testing.T) { @@ -93,7 +93,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore("appsec: ", telemetry.LogPrefix) logStartup(tracer) require.Len(t, tp.Logs(), 2) - assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sample_rate_limit":"1000.001","trace_sampling_rules":\[{"service":"mysql","sample_rate":0\.75}\],"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":{"initial_service":"new_service"},"tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"health_metrics_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"DataStreams":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\]}`, tp.Logs()[1]) + assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sample_rate_limit":"1000.001","trace_sampling_rules":\[{"service":"mysql","sample_rate":0\.75}\],"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":{"initial_service":"new_service"},"tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"health_metrics_enabled":true,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, tp.Logs()[1]) }) t.Run("errors", func(t *testing.T) { @@ -107,7 +107,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore("appsec: ", telemetry.LogPrefix) logStartup(tracer) require.Len(t, tp.Logs(), 2) - assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"100","trace_sampling_rules":\[{"service":"some\.service","sample_rate":0\.234}\],"span_sampling_rules":null,"sampling_rules_error":"\\n\\tat index 1: ignoring rule {Service:other.service Rate:2}: rate is out of \[0\.0, 1\.0] range","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":((true)|(false)),"Stats":((true)|(false)),"DataStreams":((true)|(false)),"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\]}`, tp.Logs()[1]) + assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"100","trace_sampling_rules":\[{"service":"some\.service","sample_rate":0\.234}\],"span_sampling_rules":null,"sampling_rules_error":"\\n\\tat index 1: ignoring rule {Service:other.service Rate:2}: rate is out of \[0\.0, 1\.0] range","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":((true)|(false)),"Stats":((true)|(false)),"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, tp.Logs()[1]) }) t.Run("lambda", func(t *testing.T) { @@ -120,7 +120,7 @@ func TestStartupLog(t *testing.T) { tp.Ignore("appsec: ", telemetry.LogPrefix) logStartup(tracer) assert.Len(tp.Logs(), 1) - assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v0.4/traces","agent_error":"","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"disabled","trace_sampling_rules":null,"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"true","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"DataStreams":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\]}`, tp.Logs()[0]) + assert.Regexp(logPrefixRegexp+` INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test(\.exe)?","agent_url":"http://localhost:9/v0.4/traces","agent_error":"","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sample_rate_limit":"disabled","trace_sampling_rules":null,"span_sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"profiler_code_hotspots_enabled":((false)|(true)),"profiler_endpoints_enabled":((false)|(true)),"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"true","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":0},"integrations":{.*},"partial_flush_enabled":false,"partial_flush_min_spans":1000,"orchestrion":{"enabled":false},"feature_flags":\[\],"propagation_style_inject":"datadog,tracecontext","propagation_style_extract":"datadog,tracecontext"}`, tp.Logs()[0]) }) t.Run("integrations", func(t *testing.T) { @@ -152,6 +152,18 @@ func TestLogSamplingRules(t *testing.T) { assert.Regexp(logPrefixRegexp+` WARN: DIAGNOSTICS Error\(s\) parsing sampling rules: found errors:\n\tat index 4: ignoring rule {Rate:9\.10}: rate is out of \[0\.0, 1\.0] range$`, tp.Logs()[0]) } +func TestLogDefaultSampleRate(t *testing.T) { + assert := assert.New(t) + tp := new(log.RecordLogger) + tp.Ignore("appsec: ", telemetry.LogPrefix) + log.UseLogger(tp) + t.Setenv("DD_TRACE_SAMPLE_RATE", ``) + _, _, _, stop := startTestTracer(t, WithLogger(tp)) + defer stop() + + assert.Len(tp.Logs(), 0) +} + func TestLogAgentReachable(t *testing.T) { assert := assert.New(t) tp := new(log.RecordLogger) diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index ce2ae11907..db46fcd5ba 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -25,6 +25,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema" @@ -95,6 +96,7 @@ var contribIntegrations = map[string]struct { "github.com/urfave/negroni": {"Negroni", false}, "github.com/valyala/fasthttp": {"FastHTTP", false}, "github.com/zenazn/goji": {"Goji", false}, + "log/slog": {"log/slog", false}, } var ( @@ -274,6 +276,9 @@ type config struct { // globalSampleRate holds sample rate read from environment variables. globalSampleRate float64 + + // ciVisibilityEnabled controls if the tracer is loaded with CI Visibility mode. default false + ciVisibilityEnabled bool } // orchestrionConfig contains Orchestrion configuration. @@ -305,15 +310,19 @@ const partialFlushMinSpansDefault = 1000 func newConfig(opts ...StartOption) *config { c := new(config) c.sampler = NewAllSampler() - defaultRate, err := strconv.ParseFloat(getDDorOtelConfig("sampleRate"), 64) - if err != nil { - log.Warn("ignoring DD_TRACE_SAMPLE_RATE, error: %v", err) - defaultRate = math.NaN() - } else if defaultRate < 0.0 || defaultRate > 1.0 { - log.Warn("ignoring DD_TRACE_SAMPLE_RATE: out of range %f", defaultRate) - defaultRate = math.NaN() + sampleRate := math.NaN() + if r := getDDorOtelConfig("sampleRate"); r != "" { + var err error + sampleRate, err = strconv.ParseFloat(r, 64) + if err != nil { + log.Warn("ignoring DD_TRACE_SAMPLE_RATE, error: %v", err) + sampleRate = math.NaN() + } else if sampleRate < 0.0 || sampleRate > 1.0 { + log.Warn("ignoring DD_TRACE_SAMPLE_RATE: out of range %f", sampleRate) + sampleRate = math.NaN() + } } - c.globalSampleRate = defaultRate + c.globalSampleRate = sampleRate c.httpClientTimeout = time.Second * 10 // 10 seconds if v := os.Getenv("OTEL_LOGS_EXPORTER"); v != "" { @@ -535,6 +544,14 @@ func newConfig(opts ...StartOption) *config { globalTagsOrigin := c.globalTags.cfgOrigin c.initGlobalTags(c.globalTags.get(), globalTagsOrigin) + // Check if CI Visibility mode is enabled + if internal.BoolEnv(constants.CIVisibilityEnabledEnvironmentVariable, false) { + c.ciVisibilityEnabled = true // Enable CI Visibility mode + c.httpClientTimeout = time.Second * 45 // Increase timeout up to 45 seconds (same as other tracers in CIVis mode) + c.logStartup = false // If we are in CI Visibility mode we don't want to log the startup to stdout to avoid polluting the output + c.transport = newCiVisibilityTransport(c) // Replace the default transport with the CI Visibility transport + } + return c } @@ -603,10 +620,6 @@ type agentFeatures struct { // the /v0.6/stats endpoint. Stats bool - // DataStreams reports whether the agent can receive data streams stats on - // the /v0.1/pipeline_stats endpoint. - DataStreams bool - // StatsdPort specifies the Dogstatsd port as provided by the agent. // If it's the default, it will be 0, which means 8125. StatsdPort int @@ -655,8 +668,6 @@ func loadAgentFeatures(agentDisabled bool, agentURL *url.URL, httpClient *http.C switch endpoint { case "/v0.6/stats": features.Stats = true - case "/v0.1/pipeline_stats": - features.DataStreams = true } } features.featureFlags = make(map[string]struct{}, len(info.FeatureFlags)) diff --git a/ddtrace/tracer/option_test.go b/ddtrace/tracer/option_test.go index 51efde3363..a14c3e6805 100644 --- a/ddtrace/tracer/option_test.go +++ b/ddtrace/tracer/option_test.go @@ -136,6 +136,8 @@ func TestAutoDetectStatsd(t *testing.T) { require.NoError(t, err) defer statsd.Close() require.Equal(t, cfg.dogstatsdAddr, "unix://"+addr) + // Ensure globalconfig also gets the auto-detected UDS address + require.Equal(t, "unix://"+addr, globalconfig.DogstatsdAddr()) statsd.Count("name", 1, []string{"tag"}, 1) buf := make([]byte, 17) @@ -255,7 +257,7 @@ func TestAgentIntegration(t *testing.T) { defer clearIntegrationsForTests() cfg.loadContribIntegrations(nil) - assert.Equal(t, len(cfg.integrations), 55) + assert.Equal(t, 56, len(cfg.integrations)) for integrationName, v := range cfg.integrations { assert.False(t, v.Instrumented, "integrationName=%s", integrationName) } @@ -535,6 +537,20 @@ func TestTracerOptionsDefaults(t *testing.T) { assert.Equal(t, c.dogstatsdAddr, "10.1.0.12:4002") assert.Equal(t, globalconfig.DogstatsdAddr(), "10.1.0.12:4002") }) + t.Run("uds", func(t *testing.T) { + assert := assert.New(t) + dir, err := os.MkdirTemp("", "socket") + if err != nil { + t.Fatal("Failed to create socket") + } + addr := filepath.Join(dir, "dsd.socket") + defer os.RemoveAll(addr) + tracer := newTracer(WithDogstatsdAddress("unix://" + addr)) + defer tracer.Stop() + c := tracer.config + assert.Equal("unix://"+addr, c.dogstatsdAddr) + assert.Equal("unix://"+addr, globalconfig.DogstatsdAddr()) + }) }) t.Run("env-agentAddr", func(t *testing.T) { diff --git a/ddtrace/tracer/otel_dd_mappings.go b/ddtrace/tracer/otel_dd_mappings.go index 9af9b9478d..4800c2e480 100644 --- a/ddtrace/tracer/otel_dd_mappings.go +++ b/ddtrace/tracer/otel_dd_mappings.go @@ -89,14 +89,16 @@ func getDDorOtelConfig(configName string) string { val := os.Getenv(config.dd) if otVal := os.Getenv(config.ot); otVal != "" { + ddPrefix := "config.datadog:" + otelPrefix := "config.opentelemetry:" if val != "" { log.Warn("Both %v and %v are set, using %v=%v", config.ot, config.dd, config.dd, val) - telemetry.GlobalClient.Count(telemetry.NamespaceTracers, "otel.env.hiding", 1.0, []string{config.dd, config.ot}, true) + telemetry.GlobalClient.Count(telemetry.NamespaceTracers, "otel.env.hiding", 1.0, []string{ddPrefix + config.dd, otelPrefix + config.ot}, true) } else { v, err := config.remapper(otVal) if err != nil { log.Warn(err.Error()) - telemetry.GlobalClient.Count(telemetry.NamespaceTracers, "otel.env.invalid", 1.0, []string{config.dd, config.ot}, true) + telemetry.GlobalClient.Count(telemetry.NamespaceTracers, "otel.env.invalid", 1.0, []string{ddPrefix + config.dd, otelPrefix + config.ot}, true) } val = v } diff --git a/ddtrace/tracer/otel_dd_mappings_test.go b/ddtrace/tracer/otel_dd_mappings_test.go index 8cbaa3318f..1ecfb75149 100644 --- a/ddtrace/tracer/otel_dd_mappings_test.go +++ b/ddtrace/tracer/otel_dd_mappings_test.go @@ -36,7 +36,7 @@ func TestAssessSource(t *testing.T) { t.Setenv("OTEL_SERVICE_NAME", "123") v := getDDorOtelConfig("service") assert.Equal(t, "abc", v) - telemetryClient.AssertCalled(t, "Count", telemetry.NamespaceTracers, "otel.env.hiding", 1.0, []string{"DD_SERVICE", "OTEL_SERVICE_NAME"}, true) + telemetryClient.AssertCalled(t, "Count", telemetry.NamespaceTracers, "otel.env.hiding", 1.0, []string{"config.datadog:DD_SERVICE", "config.opentelemetry:OTEL_SERVICE_NAME"}, true) }) t.Run("invalid-ot", func(t *testing.T) { telemetryClient := new(telemetrytest.MockClient) @@ -44,6 +44,6 @@ func TestAssessSource(t *testing.T) { t.Setenv("OTEL_LOG_LEVEL", "nonesense") v := getDDorOtelConfig("debugMode") assert.Equal(t, "", v) - telemetryClient.AssertCalled(t, "Count", telemetry.NamespaceTracers, "otel.env.invalid", 1.0, []string{"DD_TRACE_DEBUG", "OTEL_LOG_LEVEL"}, true) + telemetryClient.AssertCalled(t, "Count", telemetry.NamespaceTracers, "otel.env.invalid", 1.0, []string{"config.datadog:DD_TRACE_DEBUG", "config.opentelemetry:OTEL_LOG_LEVEL"}, true) }) } diff --git a/ddtrace/tracer/span.go b/ddtrace/tracer/span.go index ee201c4f9c..14f816f9f1 100644 --- a/ddtrace/tracer/span.go +++ b/ddtrace/tracer/span.go @@ -29,6 +29,7 @@ import ( sharedinternal "gopkg.in/DataDog/dd-trace-go.v1/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/orchestrion" "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" "gopkg.in/DataDog/dd-trace-go.v1/internal/traceprof" @@ -503,6 +504,7 @@ func (s *span) Finish(opts ...ddtrace.FinishOption) { } s.finish(t) + orchestrion.GLSPopValue(sharedinternal.ActiveSpanKey) } // SetOperationName sets or changes the operation name. diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index 3f4a8f0f5f..af794c7857 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -235,7 +235,9 @@ func newUnstartedTracer(opts ...StartOption) *tracer { log.Warn("Runtime and health metrics disabled: %v", err) } var writer traceWriter - if c.logToStdout { + if c.ciVisibilityEnabled { + writer = newCiVisibilityTraceWriter(c) + } else if c.logToStdout { writer = newLogTraceWriter(c, statsd) } else { writer = newAgentTraceWriter(c, sampler, statsd) @@ -263,10 +265,7 @@ func newUnstartedTracer(opts ...StartOption) *tracer { rulesSampler.traces.setTraceSampleRules, EqualsFalseNegative) var dataStreamsProcessor *datastreams.Processor if c.dataStreamsMonitoringEnabled { - dataStreamsProcessor = datastreams.NewProcessor(statsd, c.env, c.serviceName, c.version, c.agentURL, c.httpClient, func() bool { - f := loadAgentFeatures(c.logToStdout, c.agentURL, c.httpClient) - return f.DataStreams - }) + dataStreamsProcessor = datastreams.NewProcessor(statsd, c.env, c.serviceName, c.version, c.agentURL, c.httpClient) } t := &tracer{ config: c, diff --git a/docker-compose.yaml b/docker-compose.yaml index 1657755709..4548bea1c5 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,7 @@ version: "3.3" # optional since v1.27.0 services: cassandra: - image: cassandra:3.7 + image: cassandra:3.11 environment: JVM_OPTS: "-Xms750m -Xmx750m" ports: diff --git a/go.mod b/go.mod index 4d1be14816..f1f0f21e44 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module gopkg.in/DataDog/dd-trace-go.v1 -go 1.20 +go 1.21 require ( cloud.google.com/go/pubsub v1.33.0 @@ -39,6 +39,7 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 github.com/garyburd/redigo v1.6.4 github.com/gin-gonic/gin v1.9.1 + github.com/glebarez/go-sqlite v1.22.0 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 github.com/go-chi/chi v1.5.4 github.com/go-chi/chi/v5 v5.0.10 @@ -48,7 +49,7 @@ require ( github.com/go-redis/redis/v8 v8.11.5 github.com/go-sql-driver/mysql v1.6.0 github.com/gocql/gocql v0.0.0-20220224095938-0eacd3183625 - github.com/gofiber/fiber/v2 v2.52.1 + github.com/gofiber/fiber/v2 v2.52.5 github.com/golang/protobuf v1.5.3 github.com/gomodule/redigo v1.8.9 github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b @@ -62,7 +63,7 @@ require ( github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 github.com/hashicorp/vault/api v1.9.2 github.com/hashicorp/vault/sdk v0.9.2 - github.com/jackc/pgx/v5 v5.5.4 + github.com/jackc/pgx/v5 v5.6.0 github.com/jinzhu/gorm v1.9.16 github.com/jmoiron/sqlx v1.3.5 github.com/julienschmidt/httprouter v1.3.0 @@ -79,25 +80,28 @@ require ( github.com/segmentio/kafka-go v0.4.42 github.com/sirupsen/logrus v1.9.3 github.com/spaolacci/murmur3 v1.1.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d github.com/tidwall/buntdb v1.3.0 github.com/tinylib/msgp v1.1.8 github.com/twitchtv/twirp v8.1.3+incompatible + github.com/uptrace/bun v1.1.17 + github.com/uptrace/bun/dialect/sqlitedialect v1.1.17 github.com/urfave/negroni v1.0.0 github.com/valyala/fasthttp v1.51.0 - github.com/vektah/gqlparser/v2 v2.5.8 + github.com/vektah/gqlparser/v2 v2.5.16 github.com/zenazn/goji v1.0.1 go.mongodb.org/mongo-driver v1.12.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 go.opentelemetry.io/otel v1.20.0 go.opentelemetry.io/otel/trace v1.20.0 go.uber.org/atomic v1.11.0 + golang.org/x/mod v0.14.0 golang.org/x/net v0.23.0 golang.org/x/oauth2 v0.9.0 golang.org/x/sys v0.20.0 golang.org/x/time v0.3.0 - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/api v0.128.0 google.golang.org/grpc v1.57.1 google.golang.org/protobuf v1.33.0 @@ -111,6 +115,7 @@ require ( honnef.co/go/gotraceui v0.2.0 k8s.io/apimachinery v0.23.17 k8s.io/client-go v0.23.17 + modernc.org/sqlite v1.28.0 ) require ( @@ -151,7 +156,7 @@ require ( github.com/eapache/queue v1.1.0 // indirect github.com/ebitengine/purego v0.6.0-alpha.5 // indirect github.com/elastic/elastic-transport-go/v8 v8.1.0 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -179,9 +184,9 @@ require ( github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.4 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect @@ -204,6 +209,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/compress v1.17.1 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/labstack/gommon v0.4.0 // indirect @@ -223,11 +229,12 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/tidwall/btree v1.6.0 // indirect github.com/tidwall/gjson v1.16.0 // indirect github.com/tidwall/grect v0.1.4 // indirect @@ -242,7 +249,7 @@ require ( github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/tcplisten v1.0.0 // indirect github.com/vmihailenco/bufpool v0.1.11 // indirect - github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser v0.1.2 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect @@ -254,11 +261,10 @@ require ( golang.org/x/arch v0.4.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sync v0.3.0 // indirect + golang.org/x/sync v0.5.0 // indirect golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect + golang.org/x/tools v0.16.1 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect @@ -270,7 +276,16 @@ require ( k8s.io/klog/v2 v2.30.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect + lukechampine.com/uint128 v1.3.0 // indirect mellium.im/sasl v0.3.1 // indirect + modernc.org/cc/v3 v3.41.0 // indirect + modernc.org/ccgo/v3 v3.16.15 // indirect + modernc.org/libc v1.37.6 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.7.2 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.2.0 // indirect diff --git a/go.sum b/go.sum index 024396d591..9bee13d939 100644 --- a/go.sum +++ b/go.sum @@ -328,6 +328,7 @@ cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4 cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= cloud.google.com/go/kms v1.11.0 h1:0LPJPKamw3xsVpkel1bDtK0vVJec3EyqdQOLitiD030= +cloud.google.com/go/kms v1.11.0/go.mod h1:hwdiYC0xjnWsKQQCQQmIQnS9asjYVSK6jtXm+zFqXLM= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= @@ -683,6 +684,7 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H github.com/Shopify/sarama v1.38.1 h1:lqqPUPQZ7zPqYlWpTh+LQ9bhYNu2xJL6k1SJN4WVe2A= github.com/Shopify/sarama v1.38.1/go.mod h1:iwv9a67Ha8VNa+TifujYoWGxWnu2kNVAQdSdZ4X2o5g= github.com/Shopify/toxiproxy/v2 v2.5.0 h1:i4LPT+qrSlKNtQf5QliVjdP08GyAH8+BUIc9gT0eahc= +github.com/Shopify/toxiproxy/v2 v2.5.0/go.mod h1:yhM2epWtAmel9CB8r2+L+PCmhH6yH2pITaPAo7jxJl0= github.com/actgardner/gogen-avro/v10 v10.1.0/go.mod h1:o+ybmVjEa27AAr35FRqU98DJu1fXES56uXniYFv4yDA= github.com/actgardner/gogen-avro/v10 v10.2.1/go.mod h1:QUhjeHPchheYmMDni/Nx7VB0RsT/ee8YIgGY/xpEQgQ= github.com/actgardner/gogen-avro/v9 v9.1.0/go.mod h1:nyTj6wPqDJoxM3qdnjcLv+EnMDSDFqE0qDpva2QRmKc= @@ -807,7 +809,9 @@ github.com/bradfitz/gomemcache v0.0.0-20230611145640-acc696258285 h1:Dr+ezPI5ivh github.com/bradfitz/gomemcache v0.0.0-20230611145640-acc696258285/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= +github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= @@ -915,6 +919,7 @@ github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXTLTM2Pg= +github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -1039,6 +1044,7 @@ github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v23.0.4+incompatible h1:Kd3Bh9V/rO+XpTP/BLqM+gx8z7+Yb0AA2Ibj+nNo4ek= +github.com/docker/docker v23.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= @@ -1101,8 +1107,8 @@ github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQL github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +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/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -1135,6 +1141,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= @@ -1193,6 +1201,7 @@ github.com/go-pg/pg/v10 v10.11.1/go.mod h1:ExJWndhDNNftBdw1Ow83xqpSf4WMSJK8urmXD github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= 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= @@ -1211,6 +1220,7 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -1222,8 +1232,8 @@ github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofiber/fiber/v2 v2.52.1 h1:1RoU2NS+b98o1L77sdl5mboGPiW+0Ypsi5oLmcYlgHI= -github.com/gofiber/fiber/v2 v2.52.1/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= +github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= @@ -1414,6 +1424,7 @@ github.com/hashicorp/consul/api v1.24.0 h1:u2XyStA2j0jnCiVUU7Qyrt8idjRn4ORhK6Dlv github.com/hashicorp/consul/api v1.24.0/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= +github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= @@ -1422,22 +1433,22 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= -github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -1456,6 +1467,7 @@ github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -1505,8 +1517,8 @@ github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk= -github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= -github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= github.com/jackc/puddle/v2 v2.1.2/go.mod h1:2lpufsF5mRHO6SuZkm0fNYxM6SWHfvyFj62KwNzgels= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= @@ -1569,6 +1581,7 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= @@ -1694,6 +1707,7 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= +github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= @@ -1701,6 +1715,7 @@ github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2J github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= @@ -1708,6 +1723,7 @@ github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/f github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1778,6 +1794,7 @@ github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= +github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -1788,6 +1805,7 @@ github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6i github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opencontainers/runc v1.1.6 h1:XbhB8IfG/EsnhNvZtNdLB0GBw92GYEFvKlhaJk9jUgA= +github.com/opencontainers/runc v1.1.6/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -1879,6 +1897,8 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -1964,8 +1984,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= -github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -1979,8 +1999,9 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -1991,6 +2012,7 @@ github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ github.com/testcontainers/testcontainers-go v0.14.0 h1:h0D5GaYG9mhOWr2qHdEKDXpkce/VlvaYOCzTRi6UBi8= github.com/testcontainers/testcontainers-go v0.14.0/go.mod h1:hSRGJ1G8Q5Bw2gXgPulJOLlEBaYJHeBSOkQM5JLG+JQ= github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI= +github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8= github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tidwall/buntdb v1.3.0 h1:gdhWO+/YwoB2qZMeAU9JcWWsHSYU3OvcieYgFRS0zwA= @@ -2001,6 +2023,7 @@ github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vl github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q= github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8= +github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -2026,6 +2049,10 @@ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/uptrace/bun v1.1.17 h1:qxBaEIo0hC/8O3O6GrMDKxqyT+mw5/s0Pn/n6xjyGIk= +github.com/uptrace/bun v1.1.17/go.mod h1:hATAzivtTIRsSJR4B8AXR+uABqnQxr3myKDKEf5iQ9U= +github.com/uptrace/bun/dialect/sqlitedialect v1.1.17 h1:i8NFU9r8YuavNFaYlNqi4ppn+MgoHtqLgpWQDrVTjm0= +github.com/uptrace/bun/dialect/sqlitedialect v1.1.17/go.mod h1:YF0FO4VVnY9GHNH6rM4r3STlVEBxkOc6L88Bm5X5mzA= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -2041,8 +2068,8 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/vektah/gqlparser/v2 v2.5.8 h1:pm6WOnGdzFOCfcQo9L3+xzW51mKrlwTEg4Wr7AH1JW4= -github.com/vektah/gqlparser/v2 v2.5.8/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= +github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8= +github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= @@ -2054,8 +2081,8 @@ github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1 github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= -github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -2155,6 +2182,7 @@ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -2256,8 +2284,8 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2392,8 +2420,8 @@ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2665,8 +2693,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= @@ -2674,8 +2702,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= @@ -3097,18 +3126,26 @@ k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5f k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= +lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= +modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= +modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= @@ -3117,20 +3154,37 @@ modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= +modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= +modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= +modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/apps/go.mod b/internal/apps/go.mod index cfeef59b4e..d633399144 100644 --- a/internal/apps/go.mod +++ b/internal/apps/go.mod @@ -1,9 +1,9 @@ module github.com/DataDog/dd-trace-go/internal/apps -go 1.20 +go 1.21 require ( - golang.org/x/sync v0.3.0 + golang.org/x/sync v0.5.0 gopkg.in/DataDog/dd-trace-go.v1 v1.64.0 ) @@ -23,8 +23,8 @@ require ( github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/tools v0.16.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -45,11 +45,11 @@ require ( github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 // indirect github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/tinylib/msgp v1.1.8 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/internal/apps/go.sum b/internal/apps/go.sum index 6f91099884..4156bb8e3d 100644 --- a/internal/apps/go.sum +++ b/internal/apps/go.sum @@ -37,12 +37,16 @@ github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4/go.mod h1:I5sHm0Y github.com/ebitengine/purego v0.6.0-alpha.5 h1:EYID3JOAdmQ4SNZYJHu9V6IqOeRQDBYxqKAg9PyoHFY= github.com/ebitengine/purego v0.6.0-alpha.5/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= @@ -65,12 +69,15 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= @@ -82,6 +89,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -98,7 +107,8 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -106,8 +116,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -116,15 +126,17 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -135,8 +147,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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= @@ -166,25 +178,34 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg= +google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -honnef.co/go/gotraceui v0.2.0 h1:dmNsfQ9Vl3GwbiVD7Z8d/osC6WtGGrasyrC2suc4ZIQ= +modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= +modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= diff --git a/internal/appsec/dyngo/operation.go b/internal/appsec/dyngo/operation.go index e16d357e9d..884a36ccaf 100644 --- a/internal/appsec/dyngo/operation.go +++ b/internal/appsec/dyngo/operation.go @@ -21,11 +21,12 @@ package dyngo import ( + "context" + "gopkg.in/DataDog/dd-trace-go.v1/internal/orchestrion" "sync" - "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "go.uber.org/atomic" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) // Operation interface type allowing to register event listeners to the @@ -61,6 +62,9 @@ type ResultOf[O Operation] interface { // dispatch calls to the underlying event listener function. type EventListener[O Operation, T any] func(O, T) +// contextKey is used to store in a context.Context the ongoing Operation +type contextKey struct{} + // Atomic *Operation so we can atomically read or swap it. var rootOperation atomic.Pointer[Operation] @@ -86,6 +90,10 @@ type operation struct { disabled bool mu sync.RWMutex + + // inContext is used to determine if RegisterOperation was called to put the Operation in the context tree. + // If so we need to remove it from the context tree when the Operation is finished. + inContext bool } func (o *operation) Parent() Operation { @@ -140,6 +148,17 @@ func NewOperation(parent Operation) Operation { return &operation{parent: parentOp} } +// FromContext looks into the given context (or the GLS if orchestrion is enabled) for a parent Operation and returns it. +func FromContext(ctx context.Context) (Operation, bool) { + ctx = orchestrion.WrapContext(ctx) + if ctx == nil { + return nil, false + } + + op, ok := ctx.Value(contextKey{}).(Operation) + return op, ok +} + // StartOperation starts a new operation along with its arguments and emits a // start event with the operation arguments. func StartOperation[O Operation, E ArgOf[O]](op O, args E) { @@ -150,6 +169,19 @@ func StartOperation[O Operation, E ArgOf[O]](op O, args E) { } } +// StartAndRegisterOperation calls StartOperation and returns RegisterOperation result +func StartAndRegisterOperation[O Operation, E ArgOf[O]](ctx context.Context, op O, args E) context.Context { + StartOperation(op, args) + return RegisterOperation(ctx, op) +} + +// RegisterOperation registers the operation in the context tree. All operations that plan to have children operations +// should call this function to ensure the operation is properly linked in the context tree. +func RegisterOperation(ctx context.Context, op Operation) context.Context { + op.unwrap().inContext = true + return context.WithValue(ctx, contextKey{}, op) +} + // FinishOperation finishes the operation along with its results and emits a // finish event with the operation results. // The operation is then disabled and its event listeners removed. @@ -160,6 +192,10 @@ func FinishOperation[O Operation, E ResultOf[O]](op O, results E) { o.mu.RLock() defer o.mu.RUnlock() // Deferred and stacked on top of the previously deferred call to o.disable() + if o.inContext { + orchestrion.GLSPopValue(contextKey{}) + } + if o.disabled { return } diff --git a/internal/appsec/emitter/graphqlsec/context.go b/internal/appsec/emitter/graphqlsec/context.go deleted file mode 100644 index 189d7ad3e1..0000000000 --- a/internal/appsec/emitter/graphqlsec/context.go +++ /dev/null @@ -1,33 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -package graphqlsec - -import ( - "context" - - "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" - "gopkg.in/DataDog/dd-trace-go.v1/internal/log" -) - -type typed[T dyngo.Operation] struct{} - -// FromContext returns the operation of the given type from the context. Returns -// the zero-value of T if no such operation is found. -func FromContext[T dyngo.Operation](ctx context.Context) T { - val := ctx.Value(typed[T]{}) - if val == nil { - var zero T - log.Debug("appsec/graphqlsec: no operation of type %T found in context", zero) - return zero - } - - return val.(T) -} - -// contextWithValue creates a new context with the specified operation stored in it. -func contextWithValue[T dyngo.Operation](ctx context.Context, value T) context.Context { - return context.WithValue(ctx, typed[T]{}, value) -} diff --git a/internal/appsec/emitter/graphqlsec/execution.go b/internal/appsec/emitter/graphqlsec/execution.go index a9263e9913..33bd4844fc 100644 --- a/internal/appsec/emitter/graphqlsec/execution.go +++ b/internal/appsec/emitter/graphqlsec/execution.go @@ -11,6 +11,7 @@ package graphqlsec import ( "context" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/graphqlsec/types" @@ -20,19 +21,22 @@ import ( // StartExecutionOperation starts a new GraphQL query operation, along with the given arguments, and // emits a start event up in the operation stack. The operation is tracked on the returned context, // and can be extracted later on using FromContext. -func StartExecutionOperation(ctx context.Context, parent *types.RequestOperation, span trace.TagSetter, args types.ExecutionOperationArgs) (context.Context, *types.ExecutionOperation) { +func StartExecutionOperation(ctx context.Context, span trace.TagSetter, args types.ExecutionOperationArgs) (context.Context, *types.ExecutionOperation) { if span == nil { // The span may be nil (e.g: in case of GraphQL subscriptions with certian contribs). Child // operations might have spans however... and these should be used then. span = trace.NoopTagSetter{} } + parent, ok := dyngo.FromContext(ctx) + if !ok { + log.Debug("appsec: StartExecutionOperation: no parent operation found in context") + } + op := &types.ExecutionOperation{ Operation: dyngo.NewOperation(parent), TagSetter: span, } - newCtx := contextWithValue(ctx, op) - dyngo.StartOperation(op, args) - return newCtx, op + return dyngo.StartAndRegisterOperation(ctx, op, args), op } diff --git a/internal/appsec/emitter/graphqlsec/request.go b/internal/appsec/emitter/graphqlsec/request.go index 51e137cf09..a51624916e 100644 --- a/internal/appsec/emitter/graphqlsec/request.go +++ b/internal/appsec/emitter/graphqlsec/request.go @@ -21,18 +21,16 @@ import ( // emits a start event up in the operation stack. The operation is usually linked to tge global root // operation. The operation is tracked on the returned context, and can be extracted later on using // FromContext. -func StartRequestOperation(ctx context.Context, parent dyngo.Operation, span trace.TagSetter, args types.RequestOperationArgs) (context.Context, *types.RequestOperation) { +func StartRequestOperation(ctx context.Context, span trace.TagSetter, args types.RequestOperationArgs) (context.Context, *types.RequestOperation) { if span == nil { // The span may be nil (e.g: in case of GraphQL subscriptions with certian contribs) span = trace.NoopTagSetter{} } op := &types.RequestOperation{ - Operation: dyngo.NewOperation(parent), + Operation: dyngo.NewOperation(nil), TagSetter: span, } - newCtx := contextWithValue(ctx, op) - dyngo.StartOperation(op, args) - return newCtx, op + return dyngo.StartAndRegisterOperation(ctx, op, args), op } diff --git a/internal/appsec/emitter/graphqlsec/resolve.go b/internal/appsec/emitter/graphqlsec/resolve.go index 967a58f72d..4ef73248af 100644 --- a/internal/appsec/emitter/graphqlsec/resolve.go +++ b/internal/appsec/emitter/graphqlsec/resolve.go @@ -7,6 +7,7 @@ package graphqlsec import ( "context" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/graphqlsec/types" @@ -16,13 +17,15 @@ import ( // StartResolveOperation starts a new GraphQL Resolve operation, along with the given arguments, and // emits a start event up in the operation stack. The operation is tracked on the returned context, // and can be extracted later on using FromContext. -func StartResolveOperation(ctx context.Context, parent *types.ExecutionOperation, span trace.TagSetter, args types.ResolveOperationArgs) (context.Context, *types.ResolveOperation) { +func StartResolveOperation(ctx context.Context, span trace.TagSetter, args types.ResolveOperationArgs) (context.Context, *types.ResolveOperation) { + parent, ok := dyngo.FromContext(ctx) + if !ok { + log.Debug("appsec: StartResolveOperation: no parent operation found in context") + } + op := &types.ResolveOperation{ Operation: dyngo.NewOperation(parent), TagSetter: span, } - newCtx := contextWithValue(ctx, op) - dyngo.StartOperation(op, args) - - return newCtx, op + return dyngo.StartAndRegisterOperation(ctx, op, args), op } diff --git a/internal/appsec/emitter/grpcsec/grpc.go b/internal/appsec/emitter/grpcsec/grpc.go index 70fa5eb7af..15a7b57ae5 100644 --- a/internal/appsec/emitter/grpcsec/grpc.go +++ b/internal/appsec/emitter/grpcsec/grpc.go @@ -14,7 +14,6 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/grpcsec/types" - "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/trace" ) @@ -27,12 +26,10 @@ func StartHandlerOperation(ctx context.Context, args types.HandlerOperationArgs, Operation: dyngo.NewOperation(parent), TagsHolder: trace.NewTagsHolder(), } - newCtx := context.WithValue(ctx, listener.ContextKey{}, op) for _, cb := range setup { cb(op) } - dyngo.StartOperation(op, args) - return newCtx, op + return dyngo.StartAndRegisterOperation(ctx, op, args), op } // StartReceiveOperation starts a receive operation of a gRPC handler, along diff --git a/internal/appsec/emitter/httpsec/http.go b/internal/appsec/emitter/httpsec/http.go index 2f7ec76e49..37c56ef283 100644 --- a/internal/appsec/emitter/httpsec/http.go +++ b/internal/appsec/emitter/httpsec/http.go @@ -12,18 +12,17 @@ package httpsec import ( "context" - "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" // Blank import needed to use embed for the default blocked response payloads _ "embed" "net/http" "strings" + "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/httpsec/types" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/sharedsec" - "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/trace" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/trace/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" @@ -36,7 +35,7 @@ import ( // This function should not be called when AppSec is disabled in order to // get preciser error logs. func MonitorParsedBody(ctx context.Context, body any) error { - parent, _ := ctx.Value(listener.ContextKey{}).(*types.Operation) + parent, _ := dyngo.FromContext(ctx) if parent == nil { log.Error("appsec: parsed http body monitoring ignored: could not find the http handler instrumentation metadata in the request context: the request handler is not being monitored by a middleware function or the provided context is not the expected request context") return nil @@ -195,10 +194,9 @@ func StartOperation(ctx context.Context, args types.HandlerOperationArgs, setup Operation: dyngo.NewOperation(nil), TagsHolder: trace.NewTagsHolder(), } - newCtx := context.WithValue(ctx, listener.ContextKey{}, op) for _, cb := range setup { cb(op) } - dyngo.StartOperation(op, args) - return newCtx, op + + return dyngo.StartAndRegisterOperation(ctx, op, args), op } diff --git a/internal/appsec/emitter/httpsec/roundtripper.go b/internal/appsec/emitter/httpsec/roundtripper.go index 99e248fe6c..9df86576e6 100644 --- a/internal/appsec/emitter/httpsec/roundtripper.go +++ b/internal/appsec/emitter/httpsec/roundtripper.go @@ -12,7 +12,6 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/httpsec/types" - "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) @@ -23,7 +22,7 @@ func ProtectRoundTrip(ctx context.Context, url string) error { URL: url, } - parent, _ := ctx.Value(listener.ContextKey{}).(dyngo.Operation) + parent, _ := dyngo.FromContext(ctx) if parent == nil { // No parent operation => we can't monitor the request badInputContextOnce.Do(func() { log.Debug("appsec: outgoing http request monitoring ignored: could not find the handler " + diff --git a/internal/appsec/emitter/sharedsec/shared.go b/internal/appsec/emitter/sharedsec/shared.go index 440fe47839..ec5b4a754f 100644 --- a/internal/appsec/emitter/sharedsec/shared.go +++ b/internal/appsec/emitter/sharedsec/shared.go @@ -7,11 +7,10 @@ package sharedsec import ( "context" - "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" "reflect" + "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" - "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) @@ -60,7 +59,7 @@ func (f OnUserIDOperationStart) Call(op dyngo.Operation, v interface{}) { // A call to the WAF is made to check the user ID and an error is returned if the // user should be blocked. The return value is nil otherwise. func MonitorUser(ctx context.Context, userID string) error { - if parent, ok := ctx.Value(listener.ContextKey{}).(dyngo.Operation); ok { + if parent, ok := dyngo.FromContext(ctx); ok { return ExecuteUserIDOperation(parent, UserIDOperationArgs{UserID: userID}) } log.Error("appsec: user ID monitoring ignored: could not find the http handler instrumentation metadata in the request context: the request handler is not being monitored by a middleware function or the provided context is not the expected request context") diff --git a/internal/appsec/emitter/sqlsec/sql.go b/internal/appsec/emitter/sqlsec/sql.go new file mode 100644 index 0000000000..a29ab3c66b --- /dev/null +++ b/internal/appsec/emitter/sqlsec/sql.go @@ -0,0 +1,55 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package sqlsec + +import ( + "context" + "sync" + + "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/sqlsec/types" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) + +var badInputContextOnce sync.Once + +func ProtectSQLOperation(ctx context.Context, query, driver string) error { + opArgs := types.SQLOperationArgs{ + Query: query, + Driver: driver, + } + + parent, _ := dyngo.FromContext(ctx) + if parent == nil { // No parent operation => we can't monitor the request + badInputContextOnce.Do(func() { + log.Debug("appsec: outgoing SQL operation monitoring ignored: could not find the handler " + + "instrumentation metadata in the request context: the request handler is not being monitored by a " + + "middleware function or the incoming request context has not be forwarded correctly to the SQL connection") + }) + return nil + } + + op := &types.SQLOperation{ + Operation: dyngo.NewOperation(parent), + } + + var err *events.BlockingSecurityEvent + // TODO: move the data listener as a setup function of SQLsec.StartSQLOperation(ars, ) + dyngo.OnData(op, func(e *events.BlockingSecurityEvent) { + err = e + }) + + dyngo.StartOperation(op, opArgs) + dyngo.FinishOperation(op, types.SQLOperationRes{}) + + if err != nil { + log.Debug("appsec: outgoing SQL operation blocked by the WAF") + return err + } + + return nil +} diff --git a/internal/appsec/emitter/sqlsec/types/sql.go b/internal/appsec/emitter/sqlsec/types/sql.go new file mode 100644 index 0000000000..379eb7f722 --- /dev/null +++ b/internal/appsec/emitter/sqlsec/types/sql.go @@ -0,0 +1,27 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package types + +import ( + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" +) + +type ( + SQLOperation struct { + dyngo.Operation + } + + SQLOperationArgs struct { + // Query corresponds to the addres `server.db.statement` + Query string + // Driver corresponds to the addres `server.db.system` + Driver string + } + SQLOperationRes struct{} +) + +func (SQLOperationArgs) IsArgOf(*SQLOperation) {} +func (SQLOperationRes) IsResultOf(*SQLOperation) {} diff --git a/internal/appsec/listener/graphqlsec/graphql.go b/internal/appsec/listener/graphqlsec/graphql.go index 9a1ef48b2c..d12f224b8f 100644 --- a/internal/appsec/listener/graphqlsec/graphql.go +++ b/internal/appsec/listener/graphqlsec/graphql.go @@ -45,7 +45,7 @@ func Install(wafHandle *waf.Handle, cfg *config.Config, lim limiter.Limiter, roo type wafEventListener struct { wafHandle *waf.Handle config *config.Config - addresses map[string]struct{} + addresses listener.AddressSet limiter limiter.Limiter wafDiags waf.Diagnostics once sync.Once diff --git a/internal/appsec/listener/grpcsec/grpc.go b/internal/appsec/listener/grpcsec/grpc.go index 5b3326bddd..279bdb870b 100644 --- a/internal/appsec/listener/grpcsec/grpc.go +++ b/internal/appsec/listener/grpcsec/grpc.go @@ -18,6 +18,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener/httpsec" shared "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener/sharedsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener/sqlsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" @@ -53,7 +54,7 @@ func Install(wafHandle *waf.Handle, cfg *config.Config, lim limiter.Limiter, roo type wafEventListener struct { wafHandle *waf.Handle config *config.Config - addresses map[string]struct{} + addresses listener.AddressSet limiter limiter.Limiter wafDiags waf.Diagnostics once sync.Once @@ -112,28 +113,22 @@ func (l *wafEventListener) onEvent(op *types.HandlerOperation, handlerArgs types return } - if _, ok := l.addresses[httpsec.ServerIoNetURLAddr]; ok { + if l.isSecAddressListened(httpsec.ServerIoNetURLAddr) { httpsec.RegisterRoundTripperListener(op, &op.SecurityEventsHolder, wafCtx, l.limiter) } + if sqlsec.SQLAddressesPresent(l.addresses) { + sqlsec.RegisterSQLListener(op, &op.SecurityEventsHolder, wafCtx, l.limiter) + } + // Listen to the UserID address if the WAF rules are using it if l.isSecAddressListened(httpsec.UserIDAddr) { // UserIDOperation happens when appsec.SetUser() is called. We run the WAF and apply actions to // see if the associated user should be blocked. Since we don't control the execution flow in this case // (SetUser is SDK), we delegate the responsibility of interrupting the handler to the user. - dyngo.On(op, func(op *sharedsec.UserIDOperation, args sharedsec.UserIDOperationArgs) { - values := map[string]any{ - httpsec.UserIDAddr: args.UserID, - } - wafResult := shared.RunWAF(wafCtx, waf.RunAddressData{Persistent: values}) - if wafResult.HasEvents() { - addEvents(wafResult.Events) - log.Debug("appsec: WAF detected an authenticated user attack: %s", args.UserID) - } - if wafResult.HasActions() { - shared.ProcessActions(op, wafResult.Actions) - } - }) + dyngo.On(op, shared.MakeWAFRunListener(&op.SecurityEventsHolder, wafCtx, l.limiter, func(args sharedsec.UserIDOperationArgs) waf.RunAddressData { + return waf.RunAddressData{Persistent: map[string]any{httpsec.UserIDAddr: args.UserID}} + })) } values := make(map[string]any, 2) // 2 because the method and client ip addresses are commonly present in the rules diff --git a/internal/appsec/listener/httpsec/http.go b/internal/appsec/listener/httpsec/http.go index 1d9c750e69..ec63e90e93 100644 --- a/internal/appsec/listener/httpsec/http.go +++ b/internal/appsec/listener/httpsec/http.go @@ -17,6 +17,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/sharedsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener" shared "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener/sharedsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener/sqlsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" @@ -54,6 +55,8 @@ var supportedAddresses = listener.AddressSet{ HTTPClientIPAddr: {}, UserIDAddr: {}, ServerIoNetURLAddr: {}, + sqlsec.ServerDBStatementAddr: {}, + sqlsec.ServerDBTypeAddr: {}, } // Install registers the HTTP WAF Event Listener on the given root operation. @@ -67,7 +70,7 @@ func Install(wafHandle *waf.Handle, cfg *config.Config, lim limiter.Limiter, roo type wafEventListener struct { wafHandle *waf.Handle config *config.Config - addresses map[string]struct{} + addresses listener.AddressSet limiter limiter.Limiter wafDiags waf.Diagnostics once sync.Once @@ -109,21 +112,22 @@ func (l *wafEventListener) onEvent(op *types.Operation, args types.HandlerOperat } if _, ok := l.addresses[ServerIoNetURLAddr]; ok { - RegisterRoundTripperListener(op, &op.SecurityEventsHolder, wafCtx, l.limiter) + dyngo.On(op, shared.MakeWAFRunListener(&op.SecurityEventsHolder, wafCtx, l.limiter, func(args types.RoundTripOperationArgs) waf.RunAddressData { + return waf.RunAddressData{Ephemeral: map[string]any{ServerIoNetURLAddr: args.URL}} + })) + } + + if sqlsec.SQLAddressesPresent(l.addresses) { + sqlsec.RegisterSQLListener(op, &op.SecurityEventsHolder, wafCtx, l.limiter) } if _, ok := l.addresses[UserIDAddr]; ok { // OnUserIDOperationStart happens when appsec.SetUser() is called. We run the WAF and apply actions to // see if the associated user should be blocked. Since we don't control the execution flow in this case // (SetUser is SDK), we delegate the responsibility of interrupting the handler to the user. - dyngo.On(op, func(operation *sharedsec.UserIDOperation, args sharedsec.UserIDOperationArgs) { - wafResult := shared.RunWAF(wafCtx, waf.RunAddressData{Persistent: map[string]any{UserIDAddr: args.UserID}}) - if wafResult.HasActions() || wafResult.HasEvents() { - shared.ProcessActions(operation, wafResult.Actions) - shared.AddSecurityEvents(&op.SecurityEventsHolder, l.limiter, wafResult.Events) - log.Debug("appsec: WAF detected a suspicious user: %s", args.UserID) - } - }) + dyngo.On(op, shared.MakeWAFRunListener(&op.SecurityEventsHolder, wafCtx, l.limiter, func(args sharedsec.UserIDOperationArgs) waf.RunAddressData { + return waf.RunAddressData{Persistent: map[string]any{UserIDAddr: args.UserID}} + })) } values := make(map[string]any, 8) @@ -155,16 +159,8 @@ func (l *wafEventListener) onEvent(op *types.Operation, args types.HandlerOperat } } } - if l.canExtractSchemas() { - // This address will be passed as persistent. The WAF will keep it in store and trigger schema extraction - // for each run. - values["waf.context.processor"] = map[string]any{"extract-schema": true} - } wafResult := shared.RunWAF(wafCtx, waf.RunAddressData{Persistent: values}) - for tag, value := range wafResult.Derivatives { - op.AddSerializableTag(tag, value) - } if wafResult.HasActions() || wafResult.HasEvents() { interrupt := shared.ProcessActions(op, wafResult.Actions) shared.AddSecurityEvents(&op.SecurityEventsHolder, l.limiter, wafResult.Events) @@ -176,23 +172,21 @@ func (l *wafEventListener) onEvent(op *types.Operation, args types.HandlerOperat } if _, ok := l.addresses[ServerRequestBodyAddr]; ok { - dyngo.On(op, func(sdkBodyOp *types.SDKBodyOperation, args types.SDKBodyOperationArgs) { - wafResult := shared.RunWAF(wafCtx, waf.RunAddressData{Persistent: map[string]any{ServerRequestBodyAddr: args.Body}}) - for tag, value := range wafResult.Derivatives { - op.AddSerializableTag(tag, value) - } - if wafResult.HasActions() || wafResult.HasEvents() { - shared.ProcessActions(sdkBodyOp, wafResult.Actions) - shared.AddSecurityEvents(&op.SecurityEventsHolder, l.limiter, wafResult.Events) - log.Debug("appsec: WAF detected a suspicious request body") - } - }) + dyngo.On(op, shared.MakeWAFRunListener(&op.SecurityEventsHolder, wafCtx, l.limiter, func(args types.SDKBodyOperationArgs) waf.RunAddressData { + return waf.RunAddressData{Persistent: map[string]any{ServerRequestBodyAddr: args.Body}} + })) } dyngo.OnFinish(op, func(op *types.Operation, res types.HandlerOperationRes) { defer wafCtx.Close() - values = make(map[string]any, 2) + values = make(map[string]any, 3) + if l.canExtractSchemas() { + // This address will be passed as persistent. The WAF will keep it in store and trigger schema extraction + // for each run. + values["waf.context.processor"] = map[string]any{"extract-schema": true} + } + if _, ok := l.addresses[ServerResponseStatusAddr]; ok { // serverResponseStatusAddr is a string address, so we must format the status code... values[ServerResponseStatusAddr] = fmt.Sprintf("%d", res.Status) diff --git a/internal/appsec/listener/httpsec/roundtripper.go b/internal/appsec/listener/httpsec/roundtripper.go index e631013120..0d30e952f5 100644 --- a/internal/appsec/listener/httpsec/roundtripper.go +++ b/internal/appsec/listener/httpsec/roundtripper.go @@ -10,7 +10,6 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/httpsec/types" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener/sharedsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/trace" - "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "github.com/DataDog/appsec-internal-go/limiter" "github.com/DataDog/go-libddwaf/v3" @@ -18,15 +17,7 @@ import ( // RegisterRoundTripperListener registers a listener on outgoing HTTP client requests to run the WAF. func RegisterRoundTripperListener(op dyngo.Operation, events *trace.SecurityEventsHolder, wafCtx *waf.Context, limiter limiter.Limiter) { - dyngo.On(op, func(op *types.RoundTripOperation, args types.RoundTripOperationArgs) { - wafResult := sharedsec.RunWAF(wafCtx, waf.RunAddressData{Persistent: map[string]any{ServerIoNetURLAddr: args.URL}}) - if !wafResult.HasEvents() { - return - } - - log.Debug("appsec: WAF detected a suspicious outgoing request URL: %s", args.URL) - - sharedsec.ProcessActions(op, wafResult.Actions) - sharedsec.AddSecurityEvents(events, limiter, wafResult.Events) - }) + dyngo.On(op, sharedsec.MakeWAFRunListener(events, wafCtx, limiter, func(args types.RoundTripOperationArgs) waf.RunAddressData { + return waf.RunAddressData{Ephemeral: map[string]any{ServerIoNetURLAddr: args.URL}} + })) } diff --git a/internal/appsec/listener/listener.go b/internal/appsec/listener/listener.go index 3fd01c1174..7435b29e1e 100644 --- a/internal/appsec/listener/listener.go +++ b/internal/appsec/listener/listener.go @@ -10,9 +10,6 @@ package listener import waf "github.com/DataDog/go-libddwaf/v3" -// ContextKey is used as a key to store operations in the request's context (gRPC/HTTP) -type ContextKey struct{} - // AddressSet is a set of WAF addresses. type AddressSet map[string]struct{} diff --git a/internal/appsec/listener/sharedsec/shared.go b/internal/appsec/listener/sharedsec/shared.go index 39f4353d41..096a77b84c 100644 --- a/internal/appsec/listener/sharedsec/shared.go +++ b/internal/appsec/listener/sharedsec/shared.go @@ -38,6 +38,25 @@ func RunWAF(wafCtx *waf.Context, values waf.RunAddressData) waf.Result { return result } +func MakeWAFRunListener[O dyngo.Operation, T dyngo.ArgOf[O]]( + events *trace.SecurityEventsHolder, + wafCtx *waf.Context, + limiter limiter.Limiter, + toRunAddressData func(T) waf.RunAddressData, +) func(O, T) { + return func(op O, args T) { + wafResult := RunWAF(wafCtx, toRunAddressData(args)) + if !wafResult.HasEvents() { + return + } + + log.Debug("appsec: WAF detected a suspicious WAF event") + + ProcessActions(op, wafResult.Actions) + AddSecurityEvents(events, limiter, wafResult.Events) + } +} + // AddSecurityEvents is a helper function to add sec events to an operation taking into account the rate limiter. func AddSecurityEvents(holder *trace.SecurityEventsHolder, limiter limiter.Limiter, matches []any) { if len(matches) > 0 && limiter.Allow() { diff --git a/internal/appsec/listener/sqlsec/sql.go b/internal/appsec/listener/sqlsec/sql.go new file mode 100644 index 0000000000..6e8f046f53 --- /dev/null +++ b/internal/appsec/listener/sqlsec/sql.go @@ -0,0 +1,36 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package sqlsec + +import ( + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/sqlsec/types" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener/sharedsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/trace" + + "github.com/DataDog/appsec-internal-go/limiter" + waf "github.com/DataDog/go-libddwaf/v3" +) + +const ( + ServerDBStatementAddr = "server.db.statement" + ServerDBTypeAddr = "server.db.system" +) + +func RegisterSQLListener(op dyngo.Operation, events *trace.SecurityEventsHolder, wafCtx *waf.Context, limiter limiter.Limiter) { + dyngo.On(op, sharedsec.MakeWAFRunListener(events, wafCtx, limiter, func(args types.SQLOperationArgs) waf.RunAddressData { + return waf.RunAddressData{Ephemeral: map[string]any{ServerDBStatementAddr: args.Query, ServerDBTypeAddr: args.Driver}} + })) +} + +func SQLAddressesPresent(addresses listener.AddressSet) bool { + _, queryAddr := addresses[ServerDBStatementAddr] + _, driverAddr := addresses[ServerDBTypeAddr] + + return queryAddr || driverAddr + +} diff --git a/internal/appsec/waf_test.go b/internal/appsec/waf_test.go index c2e3d7c954..972ec592e0 100644 --- a/internal/appsec/waf_test.go +++ b/internal/appsec/waf_test.go @@ -6,8 +6,12 @@ package appsec_test import ( + "database/sql" "encoding/json" + "fmt" "io" + "log" + "math/rand" "net/http" "net/http/httptest" "net/url" @@ -15,15 +19,18 @@ import ( "strings" "testing" + internal "github.com/DataDog/appsec-internal-go/appsec" + waf "github.com/DataDog/go-libddwaf/v3" pAppsec "gopkg.in/DataDog/dd-trace-go.v1/appsec" + "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" + sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql" httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/config" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/listener/httpsec" - internal "github.com/DataDog/appsec-internal-go/appsec" - waf "github.com/DataDog/go-libddwaf/v3" + _ "github.com/glebarez/go-sqlite" "github.com/stretchr/testify/require" ) @@ -495,6 +502,149 @@ func TestAPISecurity(t *testing.T) { }) } +func prepareSQLDB(nbEntries int) (*sql.DB, error) { + const tables = ` +CREATE TABLE user ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name text NOT NULL, + email text NOT NULL, + password text NOT NULL +); +CREATE TABLE product ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name text NOT NULL, + category text NOT NULL, + price int NOT NULL +); +` + db, err := sqltrace.Open("sqlite", ":memory:", sqltrace.WithErrorCheck(func(err error) bool { + return err != nil + })) + if err != nil { + log.Fatalln("unexpected sql.Open error:", err) + } + + if _, err := db.Exec(tables); err != nil { + return nil, err + } + + for i := 0; i < nbEntries; i++ { + _, err := db.Exec( + "INSERT INTO user (name, email, password) VALUES (?, ?, ?)", + fmt.Sprintf("User#%d", i), + fmt.Sprintf("user%d@mail.com", i), + fmt.Sprintf("secret-password#%d", i)) + if err != nil { + return nil, err + } + + _, err = db.Exec( + "INSERT INTO product (name, category, price) VALUES (?, ?, ?)", + fmt.Sprintf("Product %d", i), + "sneaker", + rand.Intn(500)) + if err != nil { + return nil, err + } + } + + return db, nil +} + +func TestRASPSQLi(t *testing.T) { + t.Setenv("DD_APPSEC_RULES", "testdata/rasp.json") + appsec.Start() + defer appsec.Stop() + + if !appsec.RASPEnabled() { + t.Skip("RASP needs to be enabled for this test") + } + db, err := prepareSQLDB(10) + require.NoError(t, err) + + // Setup the http server + mux := httptrace.NewServeMux() + mux.HandleFunc("/query", func(w http.ResponseWriter, r *http.Request) { + // Subsequent spans inherit their parent from context. + q := r.URL.Query().Get("query") + rows, err := db.QueryContext(r.Context(), q) + if events.IsSecurityError(err) { + return + } + if err == nil { + rows.Close() + } + w.Write([]byte("Hello World!\n")) + }) + mux.HandleFunc("/exec", func(w http.ResponseWriter, r *http.Request) { + // Subsequent spans inherit their parent from context. + q := r.URL.Query().Get("query") + _, err := db.ExecContext(r.Context(), q) + if events.IsSecurityError(err) { + return + } + w.Write([]byte("Hello World!\n")) + }) + srv := httptest.NewServer(mux) + defer srv.Close() + + for name, tc := range map[string]struct { + query string + err error + }{ + "no-error": { + query: url.QueryEscape("SELECT 1"), + }, + "injection/SELECT": { + query: url.QueryEscape("SELECT * FROM users WHERE user=\"\" UNION ALL SELECT NULL;version()--"), + err: &events.BlockingSecurityEvent{}, + }, + "injection/UPDATE": { + query: url.QueryEscape("UPDATE users SET pwd = \"root\" WHERE id = \"\" OR 1 = 1--"), + err: &events.BlockingSecurityEvent{}, + }, + "injection/EXEC": { + query: url.QueryEscape("EXEC version(); DROP TABLE users--"), + err: &events.BlockingSecurityEvent{}, + }, + } { + for _, endpoint := range []string{"/query", "/exec"} { + t.Run(name+endpoint, func(t *testing.T) { + // Start tracer and appsec + mt := mocktracer.Start() + defer mt.Stop() + + req, err := http.NewRequest("POST", srv.URL+endpoint+"?query="+tc.query, nil) + require.NoError(t, err) + res, err := srv.Client().Do(req) + require.NoError(t, err) + defer res.Body.Close() + + spans := mt.FinishedSpans() + + require.Len(t, spans, 2) + + if tc.err != nil { + require.Equal(t, 403, res.StatusCode) + + for _, sp := range spans { + switch sp.OperationName() { + case "http.request": + require.Contains(t, sp.Tag("_dd.appsec.json"), "rasp-942-100") + case "sqlite.query": + require.NotContains(t, sp.Tags(), "error") + } + } + } else { + require.Equal(t, 200, res.StatusCode) + } + + }) + } + } + +} + // BenchmarkSampleWAFContext benchmarks the creation of a WAF context and running the WAF on a request/response pair // This is a basic sample of what could happen in a real-world scenario. func BenchmarkSampleWAFContext(b *testing.B) { diff --git a/internal/civisibility/constants/ci.go b/internal/civisibility/constants/ci.go new file mode 100644 index 0000000000..d1ca2e80b8 --- /dev/null +++ b/internal/civisibility/constants/ci.go @@ -0,0 +1,44 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package constants + +const ( + // CIJobName indicates the name of the CI job. + CIJobName = "ci.job.name" + + // CIJobURL indicates the URL of the CI job. + CIJobURL = "ci.job.url" + + // CIPipelineID indicates the ID of the CI pipeline. + CIPipelineID = "ci.pipeline.id" + + // CIPipelineName indicates the name of the CI pipeline. + CIPipelineName = "ci.pipeline.name" + + // CIPipelineNumber indicates the number of the CI pipeline. + CIPipelineNumber = "ci.pipeline.number" + + // CIPipelineURL indicates the URL of the CI pipeline. + CIPipelineURL = "ci.pipeline.url" + + // CIProviderName indicates the name of the CI provider. + CIProviderName = "ci.provider.name" + + // CIStageName indicates the name of the CI stage. + CIStageName = "ci.stage.name" + + // CINodeName indicates the name of the node in the CI environment. + CINodeName = "ci.node.name" + + // CINodeLabels indicates the labels associated with the node in the CI environment. + CINodeLabels = "ci.node.labels" + + // CIWorkspacePath records an absolute path to the directory where the project has been checked out. + CIWorkspacePath = "ci.workspace_path" + + // CIEnvVars contains environment variables used to get the pipeline correlation ID. + CIEnvVars = "_dd.ci.env_vars" +) diff --git a/internal/civisibility/constants/env.go b/internal/civisibility/constants/env.go new file mode 100644 index 0000000000..ebc00fcb69 --- /dev/null +++ b/internal/civisibility/constants/env.go @@ -0,0 +1,27 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package constants + +const ( + // CIVisibilityEnabledEnvironmentVariable indicates if CI Visibility mode is enabled. + // This environment variable should be set to "1" or "true" to enable CI Visibility mode, which activates tracing and other + // features related to CI Visibility in the Datadog platform. + CIVisibilityEnabledEnvironmentVariable = "DD_CIVISIBILITY_ENABLED" + + // CIVisibilityAgentlessEnabledEnvironmentVariable indicates if CI Visibility agentless mode is enabled. + // This environment variable should be set to "1" or "true" to enable agentless mode for CI Visibility, where traces + // are sent directly to Datadog without using a local agent. + CIVisibilityAgentlessEnabledEnvironmentVariable = "DD_CIVISIBILITY_AGENTLESS_ENABLED" + + // CIVisibilityAgentlessURLEnvironmentVariable forces the agentless URL to a custom one. + // This environment variable allows you to specify a custom URL for the agentless intake in CI Visibility mode. + CIVisibilityAgentlessURLEnvironmentVariable = "DD_CIVISIBILITY_AGENTLESS_URL" + + // APIKeyEnvironmentVariable indicates the API key to be used for agentless intake. + // This environment variable should be set to your Datadog API key, allowing the agentless mode to authenticate and + // send data directly to the Datadog platform. + APIKeyEnvironmentVariable = "DD_API_KEY" +) diff --git a/internal/civisibility/constants/git.go b/internal/civisibility/constants/git.go new file mode 100644 index 0000000000..6265be391a --- /dev/null +++ b/internal/civisibility/constants/git.go @@ -0,0 +1,52 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package constants + +const ( + // GitBranch indicates the current git branch. + // This constant is used to tag traces with the branch name being used in the CI/CD process. + GitBranch = "git.branch" + + // GitCommitAuthorDate indicates the git commit author date related to the build. + // This constant is used to tag traces with the date when the author created the commit. + GitCommitAuthorDate = "git.commit.author.date" + + // GitCommitAuthorEmail indicates the git commit author email related to the build. + // This constant is used to tag traces with the email of the author who created the commit. + GitCommitAuthorEmail = "git.commit.author.email" + + // GitCommitAuthorName indicates the git commit author name related to the build. + // This constant is used to tag traces with the name of the author who created the commit. + GitCommitAuthorName = "git.commit.author.name" + + // GitCommitCommitterDate indicates the git commit committer date related to the build. + // This constant is used to tag traces with the date when the committer applied the commit. + GitCommitCommitterDate = "git.commit.committer.date" + + // GitCommitCommitterEmail indicates the git commit committer email related to the build. + // This constant is used to tag traces with the email of the committer who applied the commit. + GitCommitCommitterEmail = "git.commit.committer.email" + + // GitCommitCommitterName indicates the git commit committer name related to the build. + // This constant is used to tag traces with the name of the committer who applied the commit. + GitCommitCommitterName = "git.commit.committer.name" + + // GitCommitMessage indicates the git commit message related to the build. + // This constant is used to tag traces with the message associated with the commit. + GitCommitMessage = "git.commit.message" + + // GitCommitSHA indicates the git commit SHA1 hash related to the build. + // This constant is used to tag traces with the SHA1 hash of the commit. + GitCommitSHA = "git.commit.sha" + + // GitRepositoryURL indicates the git repository URL related to the build. + // This constant is used to tag traces with the URL of the repository where the commit is stored. + GitRepositoryURL = "git.repository_url" + + // GitTag indicates the current git tag. + // This constant is used to tag traces with the tag name associated with the current commit. + GitTag = "git.tag" +) diff --git a/internal/civisibility/constants/os.go b/internal/civisibility/constants/os.go new file mode 100644 index 0000000000..e065f179f2 --- /dev/null +++ b/internal/civisibility/constants/os.go @@ -0,0 +1,21 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package constants + +const ( + // OSPlatform indicates the operating system family (e.g., linux, windows, darwin). + // This constant is used to tag traces with the operating system family on which the tests are running. + OSPlatform = "os.platform" + + // OSVersion indicates the version of the operating system. + // This constant is used to tag traces with the specific version of the operating system on which the tests are running. + OSVersion = "os.version" + + // OSArchitecture indicates the architecture this SDK is built for (e.g., amd64, 386, arm). + // This constant is used to tag traces with the architecture of the operating system for which the tests are built. + // Note: This could be 32-bit on a 64-bit system. + OSArchitecture = "os.architecture" +) diff --git a/internal/civisibility/constants/runtime.go b/internal/civisibility/constants/runtime.go new file mode 100644 index 0000000000..15257ef258 --- /dev/null +++ b/internal/civisibility/constants/runtime.go @@ -0,0 +1,16 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package constants + +const ( + // RuntimeName indicates the name of the runtime compiler. + // This constant is used to tag traces with the name of the runtime compiler being used (e.g., Go, JVM). + RuntimeName = "runtime.name" + + // RuntimeVersion indicates the version of the runtime compiler. + // This constant is used to tag traces with the specific version of the runtime compiler being used. + RuntimeVersion = "runtime.version" +) diff --git a/internal/civisibility/constants/span_types.go b/internal/civisibility/constants/span_types.go new file mode 100644 index 0000000000..55f3fe58ed --- /dev/null +++ b/internal/civisibility/constants/span_types.go @@ -0,0 +1,28 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package constants + +const ( + // SpanTypeTest marks a span as a test execution. + // This constant is used to indicate that the span represents a test execution. + SpanTypeTest = "test" + + // SpanTypeTestSuite marks a span as a test suite. + // This constant is used to indicate that the span represents the end of a test suite. + SpanTypeTestSuite = "test_suite_end" + + // SpanTypeTestModule marks a span as a test module. + // This constant is used to indicate that the span represents the end of a test module. + SpanTypeTestModule = "test_module_end" + + // SpanTypeTestSession marks a span as a test session. + // This constant is used to indicate that the span represents the end of a test session. + SpanTypeTestSession = "test_session_end" + + // SpanTypeSpan marks a span as a span event. + // This constant is used to indicate that the span represents a general span event. + SpanTypeSpan = "span" +) diff --git a/internal/civisibility/constants/tags.go b/internal/civisibility/constants/tags.go new file mode 100644 index 0000000000..4563cb8839 --- /dev/null +++ b/internal/civisibility/constants/tags.go @@ -0,0 +1,43 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package constants + +const ( + // Origin is a tag used to indicate the origin of the data. + // This tag helps in identifying the source of the trace data. + Origin = "_dd.origin" + + // CIAppTestOrigin defines the CIApp test origin value. + // This constant is used to tag traces that originate from CIApp test executions. + CIAppTestOrigin = "ciapp-test" + + // TestSessionIDTag defines the test session ID tag for the CI Visibility Protocol. + // This constant is used to tag traces with the ID of the test session. + TestSessionIDTag string = "test_session_id" + + // TestModuleIDTag defines the test module ID tag for the CI Visibility Protocol. + // This constant is used to tag traces with the ID of the test module. + TestModuleIDTag string = "test_module_id" + + // TestSuiteIDTag defines the test suite ID tag for the CI Visibility Protocol. + // This constant is used to tag traces with the ID of the test suite. + TestSuiteIDTag string = "test_suite_id" + + // ItrCorrelationIDTag defines the correlation ID for the intelligent test runner tag for the CI Visibility Protocol. + // This constant is used to tag traces with the correlation ID for intelligent test runs. + ItrCorrelationIDTag string = "itr_correlation_id" +) + +// Coverage tags +const ( + // CodeCoverageEnabledTag defines if code coverage has been enabled. + // This constant is used to tag traces to indicate whether code coverage measurement is enabled. + CodeCoverageEnabledTag string = "test.code_coverage.enabled" + + // CodeCoveragePercentageOfTotalLines defines the percentage of total code coverage by a session. + // This constant is used to tag traces with the percentage of code lines covered during the test session. + CodeCoveragePercentageOfTotalLines string = "test.code_coverage.lines_pct" +) diff --git a/internal/civisibility/constants/test_tags.go b/internal/civisibility/constants/test_tags.go new file mode 100644 index 0000000000..3a621a20be --- /dev/null +++ b/internal/civisibility/constants/test_tags.go @@ -0,0 +1,90 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package constants + +const ( + // TestModule indicates the test module name. + // This constant is used to tag traces with the name of the test module. + TestModule = "test.module" + + // TestSuite indicates the test suite name. + // This constant is used to tag traces with the name of the test suite. + TestSuite = "test.suite" + + // TestName indicates the test name. + // This constant is used to tag traces with the name of the test. + TestName = "test.name" + + // TestType indicates the type of the test (e.g., test, benchmark). + // This constant is used to tag traces with the type of the test. + TestType = "test.type" + + // TestFramework indicates the test framework name. + // This constant is used to tag traces with the name of the test framework. + TestFramework = "test.framework" + + // TestFrameworkVersion indicates the test framework version. + // This constant is used to tag traces with the version of the test framework. + TestFrameworkVersion = "test.framework_version" + + // TestStatus indicates the test execution status. + // This constant is used to tag traces with the execution status of the test. + TestStatus = "test.status" + + // TestSkipReason indicates the skip reason of the test. + // This constant is used to tag traces with the reason why the test was skipped. + TestSkipReason = "test.skip_reason" + + // TestSourceFile indicates the source file where the test is located. + // This constant is used to tag traces with the file path of the test source code. + TestSourceFile = "test.source.file" + + // TestSourceStartLine indicates the line of the source file where the test starts. + // This constant is used to tag traces with the line number in the source file where the test starts. + TestSourceStartLine = "test.source.start" + + // TestCodeOwners indicates the test code owners. + // This constant is used to tag traces with the code owners responsible for the test. + TestCodeOwners = "test.codeowners" + + // TestCommand indicates the test command. + // This constant is used to tag traces with the command used to execute the test. + TestCommand = "test.command" + + // TestCommandExitCode indicates the test command exit code. + // This constant is used to tag traces with the exit code of the test command. + TestCommandExitCode = "test.exit_code" + + // TestCommandWorkingDirectory indicates the test command working directory relative to the source root. + // This constant is used to tag traces with the working directory path relative to the source root. + TestCommandWorkingDirectory = "test.working_directory" +) + +// Define valid test status types. +const ( + // TestStatusPass marks test execution as passed. + // This constant is used to tag traces with a status indicating that the test passed. + TestStatusPass = "pass" + + // TestStatusFail marks test execution as failed. + // This constant is used to tag traces with a status indicating that the test failed. + TestStatusFail = "fail" + + // TestStatusSkip marks test execution as skipped. + // This constant is used to tag traces with a status indicating that the test was skipped. + TestStatusSkip = "skip" +) + +// Define valid test types. +const ( + // TestTypeTest defines test type as test. + // This constant is used to tag traces indicating that the type of test is a standard test. + TestTypeTest = "test" + + // TestTypeBenchmark defines test type as benchmark. + // This constant is used to tag traces indicating that the type of test is a benchmark. + TestTypeBenchmark = "benchmark" +) diff --git a/internal/civisibility/integrations/civisibility.go b/internal/civisibility/integrations/civisibility.go new file mode 100644 index 0000000000..aaa92c04bb --- /dev/null +++ b/internal/civisibility/integrations/civisibility.go @@ -0,0 +1,118 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package integrations + +import ( + "os" + "os/signal" + "regexp" + "strings" + "sync" + "syscall" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils" +) + +// ciVisibilityCloseAction defines an action to be executed when CI visibility is closing. +type ciVisibilityCloseAction func() + +var ( + // ciVisibilityInitializationOnce ensures we initialize the CI visibility tracer only once. + ciVisibilityInitializationOnce sync.Once + + // closeActions holds CI visibility close actions. + closeActions []ciVisibilityCloseAction + + // closeActionsMutex synchronizes access to closeActions. + closeActionsMutex sync.Mutex + + // mTracer contains the mock tracer instance for testing purposes + mTracer mocktracer.Tracer +) + +// EnsureCiVisibilityInitialization initializes the CI visibility tracer if it hasn't been initialized already. +func EnsureCiVisibilityInitialization() { + internalCiVisibilityInitialization(func(opts []tracer.StartOption) { + // Initialize the tracer. + tracer.Start(opts...) + }) +} + +// InitializeCIVisibilityMock initialize the mocktracer for CI Visibility usage +func InitializeCIVisibilityMock() mocktracer.Tracer { + internalCiVisibilityInitialization(func([]tracer.StartOption) { + // Initialize the mocktracer + mTracer = mocktracer.Start() + }) + return mTracer +} + +func internalCiVisibilityInitialization(tracerInitializer func([]tracer.StartOption)) { + ciVisibilityInitializationOnce.Do(func() { + // Since calling this method indicates we are in CI Visibility mode, set the environment variable. + _ = os.Setenv(constants.CIVisibilityEnabledEnvironmentVariable, "1") + + // Avoid sampling rate warning (in CI Visibility mode we send all data) + _ = os.Setenv("DD_TRACE_SAMPLE_RATE", "1") + + // Preload the CodeOwner file + _ = utils.GetCodeOwners() + + // Preload all CI, Git, and CodeOwners tags. + ciTags := utils.GetCITags() + + // Check if DD_SERVICE has been set; otherwise default to the repo name (from the spec). + var opts []tracer.StartOption + if v := os.Getenv("DD_SERVICE"); v == "" { + if repoURL, ok := ciTags[constants.GitRepositoryURL]; ok { + // regex to sanitize the repository url to be used as a service name + repoRegex := regexp.MustCompile(`(?m)/([a-zA-Z0-9\\\-_.]*)$`) + matches := repoRegex.FindStringSubmatch(repoURL) + if len(matches) > 1 { + repoURL = strings.TrimSuffix(matches[1], ".git") + } + opts = append(opts, tracer.WithService(repoURL)) + } + } + + // Initialize the tracer + tracerInitializer(opts) + + // Handle SIGINT and SIGTERM signals to ensure we close all open spans and flush the tracer before exiting + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-signals + ExitCiVisibility() + os.Exit(1) + }() + }) +} + +// PushCiVisibilityCloseAction adds a close action to be executed when CI visibility exits. +func PushCiVisibilityCloseAction(action ciVisibilityCloseAction) { + closeActionsMutex.Lock() + defer closeActionsMutex.Unlock() + closeActions = append([]ciVisibilityCloseAction{action}, closeActions...) +} + +// ExitCiVisibility executes all registered close actions and stops the tracer. +func ExitCiVisibility() { + closeActionsMutex.Lock() + defer closeActionsMutex.Unlock() + defer func() { + closeActions = []ciVisibilityCloseAction{} + + tracer.Flush() + tracer.Stop() + }() + for _, v := range closeActions { + v() + } +} diff --git a/internal/civisibility/integrations/gotesting/reflections.go b/internal/civisibility/integrations/gotesting/reflections.go new file mode 100644 index 0000000000..6aaac3ed4c --- /dev/null +++ b/internal/civisibility/integrations/gotesting/reflections.go @@ -0,0 +1,103 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package gotesting + +import ( + "errors" + "reflect" + "sync" + "testing" + "unsafe" +) + +// getFieldPointerFrom gets an unsafe.Pointer (gc-safe type of pointer) to a struct field +// useful to get or set values to private field +func getFieldPointerFrom(value any, fieldName string) (unsafe.Pointer, error) { + indirectValue := reflect.Indirect(reflect.ValueOf(value)) + member := indirectValue.FieldByName(fieldName) + if member.IsValid() { + return unsafe.Pointer(member.UnsafeAddr()), nil + } + + return unsafe.Pointer(nil), errors.New("member is invalid") +} + +// TESTING + +// getInternalTestArray gets the pointer to the testing.InternalTest array inside a +// testing.M instance containing all the "root" tests +func getInternalTestArray(m *testing.M) *[]testing.InternalTest { + if ptr, err := getFieldPointerFrom(m, "tests"); err == nil { + return (*[]testing.InternalTest)(ptr) + } + return nil +} + +// BENCHMARKS + +// get the pointer to the internal benchmark array +// getInternalBenchmarkArray gets the pointer to the testing.InternalBenchmark array inside +// a testing.M instance containing all the "root" benchmarks +func getInternalBenchmarkArray(m *testing.M) *[]testing.InternalBenchmark { + if ptr, err := getFieldPointerFrom(m, "benchmarks"); err == nil { + return (*[]testing.InternalBenchmark)(ptr) + } + return nil +} + +// commonPrivateFields is collection of required private fields from testing.common +type commonPrivateFields struct { + mu *sync.RWMutex + level *int + name *string // Name of test or benchmark. +} + +// AddLevel increase or decrease the testing.common.level field value, used by +// testing.B to create the name of the benchmark test +func (c *commonPrivateFields) AddLevel(delta int) int { + c.mu.Lock() + defer c.mu.Unlock() + *c.level = *c.level + delta + return *c.level +} + +// benchmarkPrivateFields is a collection of required private fields from testing.B +// also contains a pointer to the original testing.B for easy access +type benchmarkPrivateFields struct { + commonPrivateFields + B *testing.B + benchFunc *func(b *testing.B) + result *testing.BenchmarkResult +} + +// getBenchmarkPrivateFields is a method to retrieve all required privates field from +// testing.B, returning a benchmarkPrivateFields instance +func getBenchmarkPrivateFields(b *testing.B) *benchmarkPrivateFields { + benchFields := &benchmarkPrivateFields{ + B: b, + } + + // common + if ptr, err := getFieldPointerFrom(b, "mu"); err == nil { + benchFields.mu = (*sync.RWMutex)(ptr) + } + if ptr, err := getFieldPointerFrom(b, "level"); err == nil { + benchFields.level = (*int)(ptr) + } + if ptr, err := getFieldPointerFrom(b, "name"); err == nil { + benchFields.name = (*string)(ptr) + } + + // benchmark + if ptr, err := getFieldPointerFrom(b, "benchFunc"); err == nil { + benchFields.benchFunc = (*func(b *testing.B))(ptr) + } + if ptr, err := getFieldPointerFrom(b, "result"); err == nil { + benchFields.result = (*testing.BenchmarkResult)(ptr) + } + + return benchFields +} diff --git a/internal/civisibility/integrations/gotesting/reflections_test.go b/internal/civisibility/integrations/gotesting/reflections_test.go new file mode 100644 index 0000000000..453cd7a0e9 --- /dev/null +++ b/internal/civisibility/integrations/gotesting/reflections_test.go @@ -0,0 +1,150 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package gotesting + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestGetFieldPointerFrom tests the getFieldPointerFrom function. +func TestGetFieldPointerFrom(t *testing.T) { + // Create a mock struct with a private field + mockStruct := struct { + privateField string + }{ + privateField: "testValue", + } + + // Attempt to get a pointer to the private field + ptr, err := getFieldPointerFrom(&mockStruct, "privateField") + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + if ptr == nil { + t.Fatal("Expected a valid pointer, got nil") + } + + // Dereference the pointer to get the actual value + actualValue := (*string)(ptr) + if *actualValue != mockStruct.privateField { + t.Fatalf("Expected 'testValue', got %s", *actualValue) + } + + // Modify the value through the pointer + *actualValue = "modified value" + if *actualValue != mockStruct.privateField { + t.Fatalf("Expected 'modified value', got %s", mockStruct.privateField) + } + + // Attempt to get a pointer to a non-existent field + _, err = getFieldPointerFrom(&mockStruct, "nonExistentField") + if err == nil { + t.Fatal("Expected an error for non-existent field, got nil") + } +} + +// TestGetInternalTestArray tests the getInternalTestArray function. +func TestGetInternalTestArray(t *testing.T) { + assert := assert.New(t) + + // Get the internal test array from the mock testing.M + tests := getInternalTestArray(currentM) + assert.NotNil(tests) + + // Check that the test array contains the expected test + var testNames []string + for _, v := range *tests { + testNames = append(testNames, v.Name) + assert.NotNil(v.F) + } + + assert.Contains(testNames, "TestGetFieldPointerFrom") + assert.Contains(testNames, "TestGetInternalTestArray") + assert.Contains(testNames, "TestGetInternalBenchmarkArray") + assert.Contains(testNames, "TestCommonPrivateFields_AddLevel") + assert.Contains(testNames, "TestGetBenchmarkPrivateFields") +} + +// TestGetInternalBenchmarkArray tests the getInternalBenchmarkArray function. +func TestGetInternalBenchmarkArray(t *testing.T) { + assert := assert.New(t) + + // Get the internal benchmark array from the mock testing.M + benchmarks := getInternalBenchmarkArray(currentM) + assert.NotNil(benchmarks) + + // Check that the benchmark array contains the expected benchmark + var testNames []string + for _, v := range *benchmarks { + testNames = append(testNames, v.Name) + assert.NotNil(v.F) + } + + assert.Contains(testNames, "BenchmarkDummy") +} + +// TestCommonPrivateFields_AddLevel tests the AddLevel method of commonPrivateFields. +func TestCommonPrivateFields_AddLevel(t *testing.T) { + // Create a commonPrivateFields struct with a mutex and a level + level := 1 + commonFields := &commonPrivateFields{ + mu: &sync.RWMutex{}, + level: &level, + } + + // Add a level and check the new level + newLevel := commonFields.AddLevel(1) + if newLevel != 2 || newLevel != *commonFields.level { + t.Fatalf("Expected level to be 2, got %d", newLevel) + } + + // Subtract a level and check the new level + newLevel = commonFields.AddLevel(-1) + if newLevel != 1 || newLevel != *commonFields.level { + t.Fatalf("Expected level to be 1, got %d", newLevel) + } +} + +// TestGetBenchmarkPrivateFields tests the getBenchmarkPrivateFields function. +func TestGetBenchmarkPrivateFields(t *testing.T) { + // Create a new testing.B instance + b := &testing.B{} + + // Get the private fields of the benchmark + benchFields := getBenchmarkPrivateFields(b) + if benchFields == nil { + t.Fatal("Expected a valid benchmarkPrivateFields, got nil") + } + + // Set values to the private fields + *benchFields.name = "BenchmarkTest" + *benchFields.level = 1 + *benchFields.benchFunc = func(b *testing.B) {} + *benchFields.result = testing.BenchmarkResult{} + + // Check that the private fields have the expected values + if benchFields.level == nil || *benchFields.level != 1 { + t.Fatalf("Expected level to be 1, got %v", *benchFields.level) + } + + if benchFields.name == nil || *benchFields.name != b.Name() { + t.Fatalf("Expected name to be 'BenchmarkTest', got %v", *benchFields.name) + } + + if benchFields.benchFunc == nil { + t.Fatal("Expected benchFunc to be set, got nil") + } + + if benchFields.result == nil { + t.Fatal("Expected result to be set, got nil") + } +} + +func BenchmarkDummy(*testing.B) {} diff --git a/internal/civisibility/integrations/gotesting/testing.go b/internal/civisibility/integrations/gotesting/testing.go new file mode 100644 index 0000000000..f1c35c726a --- /dev/null +++ b/internal/civisibility/integrations/gotesting/testing.go @@ -0,0 +1,353 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package gotesting + +import ( + "fmt" + "os" + "reflect" + "runtime" + "strings" + "sync/atomic" + "testing" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils" +) + +const ( + // testFramework represents the name of the testing framework. + testFramework = "golang.org/pkg/testing" +) + +var ( + // session represents the CI visibility test session. + session integrations.DdTestSession + + // testInfos holds information about the instrumented tests. + testInfos []*testingTInfo + + // benchmarkInfos holds information about the instrumented benchmarks. + benchmarkInfos []*testingBInfo + + // modulesCounters keeps track of the number of tests per module. + modulesCounters = map[string]*int32{} + + // suitesCounters keeps track of the number of tests per suite. + suitesCounters = map[string]*int32{} +) + +type ( + // commonInfo holds common information about tests and benchmarks. + commonInfo struct { + moduleName string + suiteName string + testName string + } + + // testingTInfo holds information specific to tests. + testingTInfo struct { + commonInfo + originalFunc func(*testing.T) + } + + // testingBInfo holds information specific to benchmarks. + testingBInfo struct { + commonInfo + originalFunc func(b *testing.B) + } + + // M is a wrapper around testing.M to provide instrumentation. + M testing.M +) + +// Run initializes CI Visibility, instruments tests and benchmarks, and runs them. +func (ddm *M) Run() int { + integrations.EnsureCiVisibilityInitialization() + defer integrations.ExitCiVisibility() + + // Create a new test session for CI visibility. + session = integrations.CreateTestSession() + + m := (*testing.M)(ddm) + + // Instrument the internal tests for CI visibility. + ddm.instrumentInternalTests(getInternalTestArray(m)) + + // Instrument the internal benchmarks for CI visibility. + for _, v := range os.Args { + // check if benchmarking is enabled to instrument + if strings.Contains(v, "-bench") || strings.Contains(v, "test.bench") { + ddm.instrumentInternalBenchmarks(getInternalBenchmarkArray(m)) + break + } + } + + // Run the tests and benchmarks. + var exitCode = m.Run() + + // Close the session and return the exit code. + session.Close(exitCode) + return exitCode +} + +// instrumentInternalTests instruments the internal tests for CI visibility. +func (ddm *M) instrumentInternalTests(internalTests *[]testing.InternalTest) { + if internalTests != nil { + // Extract info from internal tests + testInfos = make([]*testingTInfo, len(*internalTests)) + for idx, test := range *internalTests { + moduleName, suiteName := utils.GetModuleAndSuiteName(reflect.Indirect(reflect.ValueOf(test.F)).Pointer()) + testInfo := &testingTInfo{ + originalFunc: test.F, + commonInfo: commonInfo{ + moduleName: moduleName, + suiteName: suiteName, + testName: test.Name, + }, + } + + // Initialize module and suite counters if not already present. + if _, ok := modulesCounters[moduleName]; !ok { + var v int32 + modulesCounters[moduleName] = &v + } + // Increment the test count in the module. + atomic.AddInt32(modulesCounters[moduleName], 1) + + if _, ok := suitesCounters[suiteName]; !ok { + var v int32 + suitesCounters[suiteName] = &v + } + // Increment the test count in the suite. + atomic.AddInt32(suitesCounters[suiteName], 1) + + testInfos[idx] = testInfo + } + + // Create new instrumented internal tests + newTestArray := make([]testing.InternalTest, len(*internalTests)) + for idx, testInfo := range testInfos { + newTestArray[idx] = testing.InternalTest{ + Name: testInfo.testName, + F: ddm.executeInternalTest(testInfo), + } + } + *internalTests = newTestArray + } +} + +// executeInternalTest wraps the original test function to include CI visibility instrumentation. +func (ddm *M) executeInternalTest(testInfo *testingTInfo) func(*testing.T) { + originalFunc := runtime.FuncForPC(reflect.Indirect(reflect.ValueOf(testInfo.originalFunc)).Pointer()) + return func(t *testing.T) { + // Create or retrieve the module, suite, and test for CI visibility. + module := session.GetOrCreateModuleWithFramework(testInfo.moduleName, testFramework, runtime.Version()) + suite := module.GetOrCreateSuite(testInfo.suiteName) + test := suite.CreateTest(testInfo.testName) + test.SetTestFunc(originalFunc) + setCiVisibilityTest(t, test) + defer func() { + if r := recover(); r != nil { + // Handle panic and set error information. + test.SetErrorInfo("panic", fmt.Sprint(r), utils.GetStacktrace(1)) + suite.SetTag(ext.Error, true) + module.SetTag(ext.Error, true) + test.Close(integrations.ResultStatusFail) + checkModuleAndSuite(module, suite) + integrations.ExitCiVisibility() + panic(r) + } else { + // Normal finalization: determine the test result based on its state. + if t.Failed() { + test.SetTag(ext.Error, true) + suite.SetTag(ext.Error, true) + module.SetTag(ext.Error, true) + test.Close(integrations.ResultStatusFail) + } else if t.Skipped() { + test.Close(integrations.ResultStatusSkip) + } else { + test.Close(integrations.ResultStatusPass) + } + + checkModuleAndSuite(module, suite) + } + }() + + // Execute the original test function. + testInfo.originalFunc(t) + } +} + +// instrumentInternalBenchmarks instruments the internal benchmarks for CI visibility. +func (ddm *M) instrumentInternalBenchmarks(internalBenchmarks *[]testing.InternalBenchmark) { + if internalBenchmarks != nil { + // Extract info from internal benchmarks + benchmarkInfos = make([]*testingBInfo, len(*internalBenchmarks)) + for idx, benchmark := range *internalBenchmarks { + moduleName, suiteName := utils.GetModuleAndSuiteName(reflect.Indirect(reflect.ValueOf(benchmark.F)).Pointer()) + benchmarkInfo := &testingBInfo{ + originalFunc: benchmark.F, + commonInfo: commonInfo{ + moduleName: moduleName, + suiteName: suiteName, + testName: benchmark.Name, + }, + } + + // Initialize module and suite counters if not already present. + if _, ok := modulesCounters[moduleName]; !ok { + var v int32 + modulesCounters[moduleName] = &v + } + // Increment the test count in the module. + atomic.AddInt32(modulesCounters[moduleName], 1) + + if _, ok := suitesCounters[suiteName]; !ok { + var v int32 + suitesCounters[suiteName] = &v + } + // Increment the test count in the suite. + atomic.AddInt32(suitesCounters[suiteName], 1) + + benchmarkInfos[idx] = benchmarkInfo + } + + // Create a new instrumented internal benchmarks + newBenchmarkArray := make([]testing.InternalBenchmark, len(*internalBenchmarks)) + for idx, benchmarkInfo := range benchmarkInfos { + newBenchmarkArray[idx] = testing.InternalBenchmark{ + Name: benchmarkInfo.testName, + F: ddm.executeInternalBenchmark(benchmarkInfo), + } + } + + *internalBenchmarks = newBenchmarkArray + } +} + +// executeInternalBenchmark wraps the original benchmark function to include CI visibility instrumentation. +func (ddm *M) executeInternalBenchmark(benchmarkInfo *testingBInfo) func(*testing.B) { + return func(b *testing.B) { + + // decrement level + getBenchmarkPrivateFields(b).AddLevel(-1) + + startTime := time.Now() + originalFunc := runtime.FuncForPC(reflect.Indirect(reflect.ValueOf(benchmarkInfo.originalFunc)).Pointer()) + module := session.GetOrCreateModuleWithFrameworkAndStartTime(benchmarkInfo.moduleName, testFramework, runtime.Version(), startTime) + suite := module.GetOrCreateSuiteWithStartTime(benchmarkInfo.suiteName, startTime) + test := suite.CreateTestWithStartTime(benchmarkInfo.testName, startTime) + test.SetTestFunc(originalFunc) + + // Run the original benchmark function. + var iPfOfB *benchmarkPrivateFields + var recoverFunc *func(r any) + b.Run(b.Name(), func(b *testing.B) { + // Stop the timer to perform initialization and replacements. + b.StopTimer() + + defer func() { + if r := recover(); r != nil { + // Handle panic if it occurs during benchmark execution. + if recoverFunc != nil { + fn := *recoverFunc + fn(r) + } + panic(r) + } + }() + + // Enable allocation reporting. + b.ReportAllocs() + // Retrieve the private fields of the inner testing.B. + iPfOfB = getBenchmarkPrivateFields(b) + // Replace the benchmark function with the original one (this must be executed only once - the first iteration[b.run1]). + *iPfOfB.benchFunc = benchmarkInfo.originalFunc + // Set the CI visibility benchmark. + setCiVisibilityBenchmark(b, test) + + // Restart the timer and execute the original benchmark function. + b.ResetTimer() + b.StartTimer() + benchmarkInfo.originalFunc(b) + }) + + endTime := time.Now() + results := iPfOfB.result + + // Set benchmark data for CI visibility. + test.SetBenchmarkData("duration", map[string]any{ + "run": results.N, + "mean": results.NsPerOp(), + }) + test.SetBenchmarkData("memory_total_operations", map[string]any{ + "run": results.N, + "mean": results.AllocsPerOp(), + "statistics.max": results.MemAllocs, + }) + test.SetBenchmarkData("mean_heap_allocations", map[string]any{ + "run": results.N, + "mean": results.AllocedBytesPerOp(), + }) + test.SetBenchmarkData("total_heap_allocations", map[string]any{ + "run": results.N, + "mean": iPfOfB.result.MemBytes, + }) + if len(results.Extra) > 0 { + mapConverted := map[string]any{} + for k, v := range results.Extra { + mapConverted[k] = v + } + test.SetBenchmarkData("extra", mapConverted) + } + + // Define a function to handle panic during benchmark finalization. + panicFunc := func(r any) { + test.SetErrorInfo("panic", fmt.Sprint(r), utils.GetStacktrace(1)) + suite.SetTag(ext.Error, true) + module.SetTag(ext.Error, true) + test.Close(integrations.ResultStatusFail) + checkModuleAndSuite(module, suite) + integrations.ExitCiVisibility() + } + recoverFunc = &panicFunc + + // Normal finalization: determine the benchmark result based on its state. + if iPfOfB.B.Failed() { + test.SetTag(ext.Error, true) + suite.SetTag(ext.Error, true) + module.SetTag(ext.Error, true) + test.CloseWithFinishTime(integrations.ResultStatusFail, endTime) + } else if iPfOfB.B.Skipped() { + test.CloseWithFinishTime(integrations.ResultStatusSkip, endTime) + } else { + test.CloseWithFinishTime(integrations.ResultStatusPass, endTime) + } + + checkModuleAndSuite(module, suite) + } +} + +// RunM runs the tests and benchmarks using CI visibility. +func RunM(m *testing.M) int { + return (*M)(m).Run() +} + +// checkModuleAndSuite checks and closes the modules and suites if all tests are executed. +func checkModuleAndSuite(module integrations.DdTestModule, suite integrations.DdTestSuite) { + // If all tests in a suite has been executed we can close the suite + if atomic.AddInt32(suitesCounters[suite.Name()], -1) <= 0 { + suite.Close() + } + + // If all tests in a module has been executed we can close the module + if atomic.AddInt32(modulesCounters[module.Name()], -1) <= 0 { + module.Close() + } +} diff --git a/internal/civisibility/integrations/gotesting/testingB.go b/internal/civisibility/integrations/gotesting/testingB.go new file mode 100644 index 0000000000..0ebd7c8159 --- /dev/null +++ b/internal/civisibility/integrations/gotesting/testingB.go @@ -0,0 +1,336 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package gotesting + +import ( + "context" + "fmt" + "reflect" + "regexp" + "runtime" + "sync" + "sync/atomic" + "testing" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils" +) + +var ( + // ciVisibilityBenchmarks holds a map of *testing.B to civisibility.DdTest for tracking benchmarks. + ciVisibilityBenchmarks = map[*testing.B]integrations.DdTest{} + + // ciVisibilityBenchmarksMutex is a read-write mutex for synchronizing access to ciVisibilityBenchmarks. + ciVisibilityBenchmarksMutex sync.RWMutex + + // subBenchmarkAutoName is a placeholder name for CI Visibility sub-benchmarks. + subBenchmarkAutoName = "[DD:TestVisibility]" + + // subBenchmarkAutoNameRegex is a regex pattern to match the sub-benchmark auto name. + subBenchmarkAutoNameRegex = regexp.MustCompile(`(?si)\/\[DD:TestVisibility\].*`) +) + +// B is a type alias for testing.B to provide additional methods for CI visibility. +type B testing.B + +// GetBenchmark is a helper to return *gotesting.B from *testing.B. +// Internally, it is just a (*gotesting.B)(b) cast. +func GetBenchmark(t *testing.B) *B { return (*B)(t) } + +// Run benchmarks f as a subbenchmark with the given name. It reports +// whether there were any failures. +// +// A subbenchmark is like any other benchmark. A benchmark that calls Run at +// least once will not be measured itself and will be called once with N=1. +func (ddb *B) Run(name string, f func(*testing.B)) bool { + // Reflect the function to obtain its pointer. + fReflect := reflect.Indirect(reflect.ValueOf(f)) + moduleName, suiteName := utils.GetModuleAndSuiteName(fReflect.Pointer()) + originalFunc := runtime.FuncForPC(fReflect.Pointer()) + + // Increment the test count in the module. + atomic.AddInt32(modulesCounters[moduleName], 1) + + // Increment the test count in the suite. + atomic.AddInt32(suitesCounters[suiteName], 1) + + pb := (*testing.B)(ddb) + return pb.Run(subBenchmarkAutoName, func(b *testing.B) { + // The sub-benchmark implementation relies on creating a dummy sub benchmark (called [DD:TestVisibility]) with + // a Run over the original sub benchmark function to get the child results without interfering measurements + // By doing this the name of the sub-benchmark are changed + // from: + // benchmark/child + // to: + // benchmark/[DD:TestVisibility]/child + // We use regex and decrement the depth level of the benchmark to restore the original name + + // Decrement level. + bpf := getBenchmarkPrivateFields(b) + bpf.AddLevel(-1) + + startTime := time.Now() + module := session.GetOrCreateModuleWithFrameworkAndStartTime(moduleName, testFramework, runtime.Version(), startTime) + suite := module.GetOrCreateSuiteWithStartTime(suiteName, startTime) + test := suite.CreateTestWithStartTime(fmt.Sprintf("%s/%s", pb.Name(), name), startTime) + test.SetTestFunc(originalFunc) + + // Restore the original name without the sub-benchmark auto name. + *bpf.name = subBenchmarkAutoNameRegex.ReplaceAllString(*bpf.name, "") + + // Run original benchmark. + var iPfOfB *benchmarkPrivateFields + var recoverFunc *func(r any) + b.Run(name, func(b *testing.B) { + // Stop the timer to do the initialization and replacements. + b.StopTimer() + + defer func() { + if r := recover(); r != nil { + if recoverFunc != nil { + fn := *recoverFunc + fn(r) + } + panic(r) + } + }() + + // First time we get the private fields of the inner testing.B. + iPfOfB = getBenchmarkPrivateFields(b) + // Replace this function with the original one (executed only once - the first iteration[b.run1]). + *iPfOfB.benchFunc = f + // Set b to the CI visibility test. + setCiVisibilityBenchmark(b, test) + + // Enable the timer again. + b.ResetTimer() + b.StartTimer() + + // Execute original func + f(b) + }) + + endTime := time.Now() + results := iPfOfB.result + + // Set benchmark data for CI visibility. + test.SetBenchmarkData("duration", map[string]any{ + "run": results.N, + "mean": results.NsPerOp(), + }) + test.SetBenchmarkData("memory_total_operations", map[string]any{ + "run": results.N, + "mean": results.AllocsPerOp(), + "statistics.max": results.MemAllocs, + }) + test.SetBenchmarkData("mean_heap_allocations", map[string]any{ + "run": results.N, + "mean": results.AllocedBytesPerOp(), + }) + test.SetBenchmarkData("total_heap_allocations", map[string]any{ + "run": results.N, + "mean": iPfOfB.result.MemBytes, + }) + if len(results.Extra) > 0 { + mapConverted := map[string]any{} + for k, v := range results.Extra { + mapConverted[k] = v + } + test.SetBenchmarkData("extra", mapConverted) + } + + // Define a function to handle panic during benchmark finalization. + panicFunc := func(r any) { + test.SetErrorInfo("panic", fmt.Sprint(r), utils.GetStacktrace(1)) + suite.SetTag(ext.Error, true) + module.SetTag(ext.Error, true) + test.Close(integrations.ResultStatusFail) + checkModuleAndSuite(module, suite) + integrations.ExitCiVisibility() + } + recoverFunc = &panicFunc + + // Normal finalization: determine the benchmark result based on its state. + if iPfOfB.B.Failed() { + test.SetTag(ext.Error, true) + suite.SetTag(ext.Error, true) + module.SetTag(ext.Error, true) + test.CloseWithFinishTime(integrations.ResultStatusFail, endTime) + } else if iPfOfB.B.Skipped() { + test.CloseWithFinishTime(integrations.ResultStatusSkip, endTime) + } else { + test.CloseWithFinishTime(integrations.ResultStatusPass, endTime) + } + + checkModuleAndSuite(module, suite) + }) +} + +// Context returns the CI Visibility context of the Test span. +// This may be used to create test's children spans useful for +// integration tests. +func (ddb *B) Context() context.Context { + b := (*testing.B)(ddb) + ciTest := getCiVisibilityBenchmark(b) + if ciTest != nil { + return ciTest.Context() + } + + return context.Background() +} + +// Fail marks the function as having failed but continues execution. +func (ddb *B) Fail() { ddb.getBWithError("Fail", "failed test").Fail() } + +// FailNow marks the function as having failed and stops its execution +// by calling runtime.Goexit (which then runs all deferred calls in the +// current goroutine). Execution will continue at the next test or benchmark. +// FailNow must be called from the goroutine running the test or benchmark function, +// not from other goroutines created during the test. Calling FailNow does not stop +// those other goroutines. +func (ddb *B) FailNow() { + b := ddb.getBWithError("FailNow", "failed test") + integrations.ExitCiVisibility() + b.FailNow() +} + +// Error is equivalent to Log followed by Fail. +func (ddb *B) Error(args ...any) { ddb.getBWithError("Error", fmt.Sprint(args...)).Error(args...) } + +// Errorf is equivalent to Logf followed by Fail. +func (ddb *B) Errorf(format string, args ...any) { + ddb.getBWithError("Errorf", fmt.Sprintf(format, args...)).Errorf(format, args...) +} + +// Fatal is equivalent to Log followed by FailNow. +func (ddb *B) Fatal(args ...any) { ddb.getBWithError("Fatal", fmt.Sprint(args...)).Fatal(args...) } + +// Fatalf is equivalent to Logf followed by FailNow. +func (ddb *B) Fatalf(format string, args ...any) { + ddb.getBWithError("Fatalf", fmt.Sprintf(format, args...)).Fatalf(format, args...) +} + +// Skip is equivalent to Log followed by SkipNow. +func (ddb *B) Skip(args ...any) { ddb.getBWithSkip(fmt.Sprint(args...)).Skip(args...) } + +// Skipf is equivalent to Logf followed by SkipNow. +func (ddb *B) Skipf(format string, args ...any) { + ddb.getBWithSkip(fmt.Sprintf(format, args...)).Skipf(format, args...) +} + +// SkipNow marks the test as having been skipped and stops its execution +// by calling runtime.Goexit. If a test fails (see Error, Errorf, Fail) and is then skipped, +// it is still considered to have failed. Execution will continue at the next test or benchmark. +// SkipNow must be called from the goroutine running the test, not from other goroutines created +// during the test. Calling SkipNow does not stop those other goroutines. +func (ddb *B) SkipNow() { + b := (*testing.B)(ddb) + ciTest := getCiVisibilityBenchmark(b) + if ciTest != nil { + ciTest.Close(integrations.ResultStatusSkip) + } + + b.SkipNow() +} + +// StartTimer starts timing a test. This function is called automatically +// before a benchmark starts, but it can also be used to resume timing after +// a call to StopTimer. +func (ddb *B) StartTimer() { (*testing.B)(ddb).StartTimer() } + +// StopTimer stops timing a test. This can be used to pause the timer +// while performing complex initialization that you don't want to measure. +func (ddb *B) StopTimer() { (*testing.B)(ddb).StopTimer() } + +// ReportAllocs enables malloc statistics for this benchmark. +// It is equivalent to setting -test.benchmem, but it only affects the +// benchmark function that calls ReportAllocs. +func (ddb *B) ReportAllocs() { (*testing.B)(ddb).ReportAllocs() } + +// ResetTimer zeroes the elapsed benchmark time and memory allocation counters +// and deletes user-reported metrics. It does not affect whether the timer is running. +func (ddb *B) ResetTimer() { (*testing.B)(ddb).ResetTimer() } + +// Elapsed returns the measured elapsed time of the benchmark. +// The duration reported by Elapsed matches the one measured by +// StartTimer, StopTimer, and ResetTimer. +func (ddb *B) Elapsed() time.Duration { + return (*testing.B)(ddb).Elapsed() +} + +// ReportMetric adds "n unit" to the reported benchmark results. +// If the metric is per-iteration, the caller should divide by b.N, +// and by convention units should end in "/op". +// ReportMetric overrides any previously reported value for the same unit. +// ReportMetric panics if unit is the empty string or if unit contains +// any whitespace. +// If unit is a unit normally reported by the benchmark framework itself +// (such as "allocs/op"), ReportMetric will override that metric. +// Setting "ns/op" to 0 will suppress that built-in metric. +func (ddb *B) ReportMetric(n float64, unit string) { (*testing.B)(ddb).ReportMetric(n, unit) } + +// RunParallel runs a benchmark in parallel. +// It creates multiple goroutines and distributes b.N iterations among them. +// The number of goroutines defaults to GOMAXPROCS. To increase parallelism for +// non-CPU-bound benchmarks, call SetParallelism before RunParallel. +// RunParallel is usually used with the go test -cpu flag. +// +// The body function will be run in each goroutine. It should set up any +// goroutine-local state and then iterate until pb.Next returns false. +// It should not use the StartTimer, StopTimer, or ResetTimer functions, +// because they have global effect. It should also not call Run. +// +// RunParallel reports ns/op values as wall time for the benchmark as a whole, +// not the sum of wall time or CPU time over each parallel goroutine. +func (ddb *B) RunParallel(body func(*testing.PB)) { (*testing.B)(ddb).RunParallel(body) } + +// SetBytes records the number of bytes processed in a single operation. +// If this is called, the benchmark will report ns/op and MB/s. +func (ddb *B) SetBytes(n int64) { (*testing.B)(ddb).SetBytes(n) } + +// SetParallelism sets the number of goroutines used by RunParallel to p*GOMAXPROCS. +// There is usually no need to call SetParallelism for CPU-bound benchmarks. +// If p is less than 1, this call will have no effect. +func (ddb *B) SetParallelism(p int) { (*testing.B)(ddb).SetParallelism(p) } + +func (ddb *B) getBWithError(errType string, errMessage string) *testing.B { + b := (*testing.B)(ddb) + ciTest := getCiVisibilityBenchmark(b) + if ciTest != nil { + ciTest.SetErrorInfo(errType, errMessage, utils.GetStacktrace(2)) + } + return b +} + +func (ddb *B) getBWithSkip(skipReason string) *testing.B { + b := (*testing.B)(ddb) + ciTest := getCiVisibilityBenchmark(b) + if ciTest != nil { + ciTest.CloseWithFinishTimeAndSkipReason(integrations.ResultStatusSkip, time.Now(), skipReason) + } + return b +} + +// getCiVisibilityBenchmark retrieves the CI visibility benchmark associated with a given *testing.B. +func getCiVisibilityBenchmark(b *testing.B) integrations.DdTest { + ciVisibilityBenchmarksMutex.RLock() + defer ciVisibilityBenchmarksMutex.RUnlock() + + if v, ok := ciVisibilityBenchmarks[b]; ok { + return v + } + + return nil +} + +// setCiVisibilityBenchmark associates a CI visibility benchmark with a given *testing.B. +func setCiVisibilityBenchmark(b *testing.B, ciTest integrations.DdTest) { + ciVisibilityBenchmarksMutex.Lock() + defer ciVisibilityBenchmarksMutex.Unlock() + ciVisibilityBenchmarks[b] = ciTest +} diff --git a/internal/civisibility/integrations/gotesting/testingT.go b/internal/civisibility/integrations/gotesting/testingT.go new file mode 100644 index 0000000000..2ac53c3fa8 --- /dev/null +++ b/internal/civisibility/integrations/gotesting/testingT.go @@ -0,0 +1,216 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package gotesting + +import ( + "context" + "fmt" + "reflect" + "runtime" + "sync" + "sync/atomic" + "testing" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils" +) + +var ( + // ciVisibilityTests holds a map of *testing.T to civisibility.DdTest for tracking tests. + ciVisibilityTests = map[*testing.T]integrations.DdTest{} + + // ciVisibilityTestsMutex is a read-write mutex for synchronizing access to ciVisibilityTests. + ciVisibilityTestsMutex sync.RWMutex +) + +// T is a type alias for testing.T to provide additional methods for CI visibility. +type T testing.T + +// GetTest is a helper to return *gotesting.T from *testing.T. +// Internally, it is just a (*gotesting.T)(t) cast. +func GetTest(t *testing.T) *T { + return (*T)(t) +} + +// Run runs f as a subtest of t called name. It runs f in a separate goroutine +// and blocks until f returns or calls t.Parallel to become a parallel test. +// Run reports whether f succeeded (or at least did not fail before calling t.Parallel). +// +// Run may be called simultaneously from multiple goroutines, but all such calls +// must return before the outer test function for t returns. +func (ddt *T) Run(name string, f func(*testing.T)) bool { + // Reflect the function to obtain its pointer. + fReflect := reflect.Indirect(reflect.ValueOf(f)) + moduleName, suiteName := utils.GetModuleAndSuiteName(fReflect.Pointer()) + originalFunc := runtime.FuncForPC(fReflect.Pointer()) + + // Increment the test count in the module. + atomic.AddInt32(modulesCounters[moduleName], 1) + + // Increment the test count in the suite. + atomic.AddInt32(suitesCounters[suiteName], 1) + + t := (*testing.T)(ddt) + return t.Run(name, func(t *testing.T) { + // Create or retrieve the module, suite, and test for CI visibility. + module := session.GetOrCreateModuleWithFramework(moduleName, testFramework, runtime.Version()) + suite := module.GetOrCreateSuite(suiteName) + test := suite.CreateTest(t.Name()) + test.SetTestFunc(originalFunc) + setCiVisibilityTest(t, test) + defer func() { + if r := recover(); r != nil { + // Handle panic and set error information. + test.SetErrorInfo("panic", fmt.Sprint(r), utils.GetStacktrace(1)) + test.Close(integrations.ResultStatusFail) + checkModuleAndSuite(module, suite) + integrations.ExitCiVisibility() + panic(r) + } else { + // Normal finalization: determine the test result based on its state. + if t.Failed() { + test.SetTag(ext.Error, true) + suite.SetTag(ext.Error, true) + module.SetTag(ext.Error, true) + test.Close(integrations.ResultStatusFail) + } else if t.Skipped() { + test.Close(integrations.ResultStatusSkip) + } else { + test.Close(integrations.ResultStatusPass) + } + checkModuleAndSuite(module, suite) + } + }() + + // Execute the original test function. + f(t) + }) +} + +// Context returns the CI Visibility context of the Test span. +// This may be used to create test's children spans useful for +// integration tests. +func (ddt *T) Context() context.Context { + t := (*testing.T)(ddt) + ciTest := getCiVisibilityTest(t) + if ciTest != nil { + return ciTest.Context() + } + + return context.Background() +} + +// Fail marks the function as having failed but continues execution. +func (ddt *T) Fail() { ddt.getTWithError("Fail", "failed test").Fail() } + +// FailNow marks the function as having failed and stops its execution +// by calling runtime.Goexit (which then runs all deferred calls in the +// current goroutine). Execution will continue at the next test or benchmark. +// FailNow must be called from the goroutine running the test or benchmark function, +// not from other goroutines created during the test. Calling FailNow does not stop +// those other goroutines. +func (ddt *T) FailNow() { + t := ddt.getTWithError("FailNow", "failed test") + integrations.ExitCiVisibility() + t.FailNow() +} + +// Error is equivalent to Log followed by Fail. +func (ddt *T) Error(args ...any) { ddt.getTWithError("Error", fmt.Sprint(args...)).Error(args...) } + +// Errorf is equivalent to Logf followed by Fail. +func (ddt *T) Errorf(format string, args ...any) { + ddt.getTWithError("Errorf", fmt.Sprintf(format, args...)).Errorf(format, args...) +} + +// Fatal is equivalent to Log followed by FailNow. +func (ddt *T) Fatal(args ...any) { ddt.getTWithError("Fatal", fmt.Sprint(args...)).Fatal(args...) } + +// Fatalf is equivalent to Logf followed by FailNow. +func (ddt *T) Fatalf(format string, args ...any) { + ddt.getTWithError("Fatalf", fmt.Sprintf(format, args...)).Fatalf(format, args...) +} + +// Skip is equivalent to Log followed by SkipNow. +func (ddt *T) Skip(args ...any) { ddt.getTWithSkip(fmt.Sprint(args...)).Skip(args...) } + +// Skipf is equivalent to Logf followed by SkipNow. +func (ddt *T) Skipf(format string, args ...any) { + ddt.getTWithSkip(fmt.Sprintf(format, args...)).Skipf(format, args...) +} + +// SkipNow marks the test as having been skipped and stops its execution +// by calling runtime.Goexit. If a test fails (see Error, Errorf, Fail) and is then skipped, +// it is still considered to have failed. Execution will continue at the next test or benchmark. +// SkipNow must be called from the goroutine running the test, not from other goroutines created +// during the test. Calling SkipNow does not stop those other goroutines. +func (ddt *T) SkipNow() { + t := (*testing.T)(ddt) + ciTest := getCiVisibilityTest(t) + if ciTest != nil { + ciTest.Close(integrations.ResultStatusSkip) + } + + t.SkipNow() +} + +// Parallel signals that this test is to be run in parallel with (and only with) +// other parallel tests. When a test is run multiple times due to use of +// -test.count or -test.cpu, multiple instances of a single test never run in +// parallel with each other. +func (ddt *T) Parallel() { (*testing.T)(ddt).Parallel() } + +// Deadline reports the time at which the test binary will have +// exceeded the timeout specified by the -timeout flag. +// The ok result is false if the -timeout flag indicates “no timeout” (0). +func (ddt *T) Deadline() (deadline time.Time, ok bool) { + return (*testing.T)(ddt).Deadline() +} + +// Setenv calls os.Setenv(key, value) and uses Cleanup to +// restore the environment variable to its original value +// after the test. Because Setenv affects the whole process, +// it cannot be used in parallel tests or tests with parallel ancestors. +func (ddt *T) Setenv(key, value string) { (*testing.T)(ddt).Setenv(key, value) } + +func (ddt *T) getTWithError(errType string, errMessage string) *testing.T { + t := (*testing.T)(ddt) + ciTest := getCiVisibilityTest(t) + if ciTest != nil { + ciTest.SetErrorInfo(errType, errMessage, utils.GetStacktrace(2)) + } + return t +} + +func (ddt *T) getTWithSkip(skipReason string) *testing.T { + t := (*testing.T)(ddt) + ciTest := getCiVisibilityTest(t) + if ciTest != nil { + ciTest.CloseWithFinishTimeAndSkipReason(integrations.ResultStatusSkip, time.Now(), skipReason) + } + return t +} + +// getCiVisibilityTest retrieves the CI visibility test associated with a given *testing.T. +func getCiVisibilityTest(t *testing.T) integrations.DdTest { + ciVisibilityTestsMutex.RLock() + defer ciVisibilityTestsMutex.RUnlock() + + if v, ok := ciVisibilityTests[t]; ok { + return v + } + + return nil +} + +// setCiVisibilityTest associates a CI visibility test with a given *testing.T. +func setCiVisibilityTest(t *testing.T, ciTest integrations.DdTest) { + ciVisibilityTestsMutex.Lock() + defer ciVisibilityTestsMutex.Unlock() + ciVisibilityTests[t] = ciTest +} diff --git a/internal/civisibility/integrations/gotesting/testing_test.go b/internal/civisibility/integrations/gotesting/testing_test.go new file mode 100644 index 0000000000..e45e62d15d --- /dev/null +++ b/internal/civisibility/integrations/gotesting/testing_test.go @@ -0,0 +1,353 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package gotesting + +import ( + "fmt" + "net/http" + "net/http/httptest" + "os" + "strconv" + "testing" + + ddhttp "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + ddtracer "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations" + + "github.com/stretchr/testify/assert" +) + +var currentM *testing.M +var mTracer mocktracer.Tracer + +// TestMain is the entry point for testing and runs before any test. +func TestMain(m *testing.M) { + currentM = m + mTracer = integrations.InitializeCIVisibilityMock() + + // (*M)(m).Run() cast m to gotesting.M and just run + // or use a helper method gotesting.RunM(m) + + // os.Exit((*M)(m).Run()) + _ = RunM(m) + + finishedSpans := mTracer.FinishedSpans() + // 1 session span + // 1 module span + // 1 suite span (optional 1 from reflections_test.go) + // 6 tests spans + // 7 sub stest spans + // 2 normal spans (from integration tests) + // 1 benchmark span (optional - require the -bench option) + if len(finishedSpans) < 17 { + panic("expected at least 17 finished spans, got " + strconv.Itoa(len(finishedSpans))) + } + + sessionSpans := getSpansWithType(finishedSpans, constants.SpanTypeTestSession) + if len(sessionSpans) != 1 { + panic("expected exactly 1 session span, got " + strconv.Itoa(len(sessionSpans))) + } + + moduleSpans := getSpansWithType(finishedSpans, constants.SpanTypeTestModule) + if len(moduleSpans) != 1 { + panic("expected exactly 1 module span, got " + strconv.Itoa(len(moduleSpans))) + } + + suiteSpans := getSpansWithType(finishedSpans, constants.SpanTypeTestSuite) + if len(suiteSpans) < 1 { + panic("expected at least 1 suite span, got " + strconv.Itoa(len(suiteSpans))) + } + + testSpans := getSpansWithType(finishedSpans, constants.SpanTypeTest) + if len(testSpans) < 12 { + panic("expected at least 12 suite span, got " + strconv.Itoa(len(testSpans))) + } + + httpSpans := getSpansWithType(finishedSpans, ext.SpanTypeHTTP) + if len(httpSpans) != 2 { + panic("expected exactly 2 normal spans, got " + strconv.Itoa(len(httpSpans))) + } + + os.Exit(0) +} + +// TestMyTest01 demonstrates instrumentation of InternalTests +func TestMyTest01(t *testing.T) { + assertTest(t) +} + +// TestMyTest02 demonstrates instrumentation of subtests. +func TestMyTest02(gt *testing.T) { + assertTest(gt) + + // To instrument subTests we just need to cast + // testing.T to our gotesting.T + // using: newT := (*gotesting.T)(t) + // Then all testing.T will be available but instrumented + t := (*T)(gt) + // or + t = GetTest(gt) + + t.Run("sub01", func(oT2 *testing.T) { + t2 := (*T)(oT2) // Cast the sub-test to gotesting.T + t2.Log("From sub01") + t2.Run("sub03", func(t3 *testing.T) { + t3.Log("From sub03") + }) + }) +} + +func Test_Foo(gt *testing.T) { + assertTest(gt) + t := (*T)(gt) + var tests = []struct { + name string + input string + want string + }{ + {"yellow should return color", "yellow", "color"}, + {"banana should return fruit", "banana", "fruit"}, + {"duck should return animal", "duck", "animal"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Log(test.name) + }) + } +} + +// TestWithExternalCalls demonstrates testing with external HTTP calls. +func TestWithExternalCalls(gt *testing.T) { + assertTest(gt) + t := (*T)(gt) + + // Create a new HTTP test server + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte("Hello World")) + })) + defer s.Close() + + t.Run("default", func(t *testing.T) { + + // if we want to use the test span as a parent of a child span + // we can extract the SpanContext and use it in other integrations + ctx := (*T)(t).Context() + + // Wrap the default HTTP transport for tracing + rt := ddhttp.WrapRoundTripper(http.DefaultTransport) + client := &http.Client{ + Transport: rt, + } + + // Create a new HTTP request + req, err := http.NewRequest("GET", s.URL+"/hello/world", nil) + if err != nil { + t.FailNow() + } + + // Use the span context here so the http span will appear as a child of the test + req = req.WithContext(ctx) + + res, err := client.Do(req) + if err != nil { + t.FailNow() + } + _ = res.Body.Close() + }) + + t.Run("custom-name", func(t *testing.T) { + + // we can also add custom tags to the test span by retrieving the + // context and call the `ddtracer.SpanFromContext` api + ctx := (*T)(t).Context() + span, _ := ddtracer.SpanFromContext(ctx) + + // Custom namer function for the HTTP request + customNamer := func(req *http.Request) string { + value := fmt.Sprintf("%s %s", req.Method, req.URL.Path) + + // Then we can set custom tags to that test span + span.SetTag("customNamer.Value", value) + return value + } + + rt := ddhttp.WrapRoundTripper(http.DefaultTransport, ddhttp.RTWithResourceNamer(customNamer)) + client := &http.Client{ + Transport: rt, + } + + req, err := http.NewRequest("GET", s.URL+"/hello/world", nil) + if err != nil { + t.FailNow() + } + + // Use the span context here so the http span will appear as a child of the test + req = req.WithContext(ctx) + + res, err := client.Do(req) + if err != nil { + t.FailNow() + } + _ = res.Body.Close() + }) +} + +// TestSkip demonstrates skipping a test with a message. +func TestSkip(gt *testing.T) { + assertTest(gt) + + t := (*T)(gt) + + // because we use the instrumented Skip + // the message will be reported as the skip reason. + t.Skip("Nothing to do here, skipping!") +} + +// BenchmarkFirst demonstrates benchmark instrumentation with sub-benchmarks. +func BenchmarkFirst(gb *testing.B) { + + // Same happens with sub benchmarks + // we just need to cast testing.B to gotesting.B + // using: newB := (*gotesting.B)(b) + b := (*B)(gb) + // or + b = GetBenchmark(gb) + + var mapArray []map[string]string + b.Run("child01", func(b *testing.B) { + for i := 0; i < b.N; i++ { + mapArray = append(mapArray, map[string]string{}) + } + }) + + b.Run("child02", func(b *testing.B) { + for i := 0; i < b.N; i++ { + mapArray = append(mapArray, map[string]string{}) + } + }) + + b.Run("child03", func(b *testing.B) { + GetBenchmark(b).Skip("The reason...") + }) + + _ = gb.Elapsed() +} + +func assertTest(t *testing.T) { + assert := assert.New(t) + spans := mTracer.OpenSpans() + hasSession := false + hasModule := false + hasSuite := false + hasTest := false + for _, span := range spans { + spanTags := span.Tags() + + // Assert Session + if span.Tag(ext.SpanType) == constants.SpanTypeTestSession { + assert.Contains(spanTags, constants.TestSessionIDTag) + assertCommon(assert, span) + hasSession = true + } + + // Assert Module + if span.Tag(ext.SpanType) == constants.SpanTypeTestModule { + assert.Subset(spanTags, map[string]interface{}{ + constants.TestModule: "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations/gotesting", + constants.TestFramework: "golang.org/pkg/testing", + }) + assert.Contains(spanTags, constants.TestSessionIDTag) + assert.Contains(spanTags, constants.TestModuleIDTag) + assert.Contains(spanTags, constants.TestFrameworkVersion) + assertCommon(assert, span) + hasModule = true + } + + // Assert Suite + if span.Tag(ext.SpanType) == constants.SpanTypeTestSuite { + assert.Subset(spanTags, map[string]interface{}{ + constants.TestModule: "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations/gotesting", + constants.TestFramework: "golang.org/pkg/testing", + }) + assert.Contains(spanTags, constants.TestSessionIDTag) + assert.Contains(spanTags, constants.TestModuleIDTag) + assert.Contains(spanTags, constants.TestSuiteIDTag) + assert.Contains(spanTags, constants.TestFrameworkVersion) + assert.Contains(spanTags, constants.TestSuite) + assertCommon(assert, span) + hasSuite = true + } + + // Assert Test + if span.Tag(ext.SpanType) == constants.SpanTypeTest { + assert.Subset(spanTags, map[string]interface{}{ + constants.TestModule: "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations/gotesting", + constants.TestFramework: "golang.org/pkg/testing", + constants.TestSuite: "testing_test.go", + constants.TestName: t.Name(), + constants.TestType: constants.TestTypeTest, + }) + assert.Contains(spanTags, constants.TestSessionIDTag) + assert.Contains(spanTags, constants.TestModuleIDTag) + assert.Contains(spanTags, constants.TestSuiteIDTag) + assert.Contains(spanTags, constants.TestFrameworkVersion) + assert.Contains(spanTags, constants.TestCodeOwners) + assert.Contains(spanTags, constants.TestSourceFile) + assert.Contains(spanTags, constants.TestSourceStartLine) + assertCommon(assert, span) + hasTest = true + } + } + + assert.True(hasSession) + assert.True(hasModule) + assert.True(hasSuite) + assert.True(hasTest) +} + +func assertCommon(assert *assert.Assertions, span mocktracer.Span) { + spanTags := span.Tags() + + assert.Subset(spanTags, map[string]interface{}{ + constants.Origin: constants.CIAppTestOrigin, + constants.TestType: constants.TestTypeTest, + }) + + assert.Contains(spanTags, ext.ResourceName) + assert.Contains(spanTags, constants.TestCommand) + assert.Contains(spanTags, constants.TestCommandWorkingDirectory) + assert.Contains(spanTags, constants.OSPlatform) + assert.Contains(spanTags, constants.OSArchitecture) + assert.Contains(spanTags, constants.OSVersion) + assert.Contains(spanTags, constants.RuntimeVersion) + assert.Contains(spanTags, constants.RuntimeName) + assert.Contains(spanTags, constants.GitRepositoryURL) + assert.Contains(spanTags, constants.GitCommitSHA) + // GitHub CI does not provide commit details + if spanTags[constants.CIProviderName] != "github" { + assert.Contains(spanTags, constants.GitCommitMessage) + assert.Contains(spanTags, constants.GitCommitAuthorEmail) + assert.Contains(spanTags, constants.GitCommitAuthorDate) + assert.Contains(spanTags, constants.GitCommitCommitterEmail) + assert.Contains(spanTags, constants.GitCommitCommitterDate) + assert.Contains(spanTags, constants.GitCommitCommitterName) + } + assert.Contains(spanTags, constants.CIWorkspacePath) +} + +func getSpansWithType(spans []mocktracer.Span, spanType string) []mocktracer.Span { + var result []mocktracer.Span + for _, span := range spans { + if span.Tag(ext.SpanType) == spanType { + result = append(result, span) + } + } + + return result +} diff --git a/internal/civisibility/integrations/manual_api.go b/internal/civisibility/integrations/manual_api.go new file mode 100644 index 0000000000..276898ad1a --- /dev/null +++ b/internal/civisibility/integrations/manual_api.go @@ -0,0 +1,218 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package integrations + +import ( + "context" + "runtime" + "sync" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils" +) + +// TestResultStatus represents the result status of a test. +type TestResultStatus int + +const ( + // ResultStatusPass indicates that the test has passed. + ResultStatusPass TestResultStatus = 0 + + // ResultStatusFail indicates that the test has failed. + ResultStatusFail TestResultStatus = 1 + + // ResultStatusSkip indicates that the test has been skipped. + ResultStatusSkip TestResultStatus = 2 +) + +// ddTslvEvent is an interface that provides common methods for CI visibility events. +type ddTslvEvent interface { + // Context returns the context of the event. + Context() context.Context + + // StartTime returns the start time of the event. + StartTime() time.Time + + // SetError sets an error on the event. + SetError(err error) + + // SetErrorInfo sets detailed error information on the event. + SetErrorInfo(errType string, message string, callstack string) + + // SetTag sets a tag on the event. + SetTag(key string, value interface{}) +} + +// DdTestSession represents a session for a set of tests. +type DdTestSession interface { + ddTslvEvent + + // Command returns the command used to run the session. + Command() string + + // Framework returns the testing framework used. + Framework() string + + // WorkingDirectory returns the working directory of the session. + WorkingDirectory() string + + // Close closes the test session with the given exit code. + Close(exitCode int) + + // CloseWithFinishTime closes the test session with the given exit code and finish time. + CloseWithFinishTime(exitCode int, finishTime time.Time) + + // GetOrCreateModule returns an existing module or creates a new one with the given name. + GetOrCreateModule(name string) DdTestModule + + // GetOrCreateModuleWithFramework returns an existing module or creates a new one with the given name, framework, and framework version. + GetOrCreateModuleWithFramework(name string, framework string, frameworkVersion string) DdTestModule + + // GetOrCreateModuleWithFrameworkAndStartTime returns an existing module or creates a new one with the given name, framework, framework version, and start time. + GetOrCreateModuleWithFrameworkAndStartTime(name string, framework string, frameworkVersion string, startTime time.Time) DdTestModule +} + +// DdTestModule represents a module within a test session. +type DdTestModule interface { + ddTslvEvent + + // Session returns the test session to which the module belongs. + Session() DdTestSession + + // Framework returns the testing framework used by the module. + Framework() string + + // Name returns the name of the module. + Name() string + + // Close closes the test module. + Close() + + // CloseWithFinishTime closes the test module with the given finish time. + CloseWithFinishTime(finishTime time.Time) + + // GetOrCreateSuite returns an existing suite or creates a new one with the given name. + GetOrCreateSuite(name string) DdTestSuite + + // GetOrCreateSuiteWithStartTime returns an existing suite or creates a new one with the given name and start time. + GetOrCreateSuiteWithStartTime(name string, startTime time.Time) DdTestSuite +} + +// DdTestSuite represents a suite of tests within a module. +type DdTestSuite interface { + ddTslvEvent + + // Module returns the module to which the suite belongs. + Module() DdTestModule + + // Name returns the name of the suite. + Name() string + + // Close closes the test suite. + Close() + + // CloseWithFinishTime closes the test suite with the given finish time. + CloseWithFinishTime(finishTime time.Time) + + // CreateTest creates a new test with the given name. + CreateTest(name string) DdTest + + // CreateTestWithStartTime creates a new test with the given name and start time. + CreateTestWithStartTime(name string, startTime time.Time) DdTest +} + +// DdTest represents an individual test within a suite. +type DdTest interface { + ddTslvEvent + + // Name returns the name of the test. + Name() string + + // Suite returns the suite to which the test belongs. + Suite() DdTestSuite + + // Close closes the test with the given status. + Close(status TestResultStatus) + + // CloseWithFinishTime closes the test with the given status and finish time. + CloseWithFinishTime(status TestResultStatus, finishTime time.Time) + + // CloseWithFinishTimeAndSkipReason closes the test with the given status, finish time, and skip reason. + CloseWithFinishTimeAndSkipReason(status TestResultStatus, finishTime time.Time, skipReason string) + + // SetTestFunc sets the function to be tested. (Sets the test.source tags and test.codeowners) + SetTestFunc(fn *runtime.Func) + + // SetBenchmarkData sets benchmark data for the test. + SetBenchmarkData(measureType string, data map[string]any) +} + +// common +var _ ddTslvEvent = (*ciVisibilityCommon)(nil) + +// ciVisibilityCommon is a struct that implements the ddTslvEvent interface and provides common functionality for CI visibility. +type ciVisibilityCommon struct { + startTime time.Time + + tags []tracer.StartSpanOption + span tracer.Span + ctx context.Context + mutex sync.Mutex + closed bool +} + +// Context returns the context of the event. +func (c *ciVisibilityCommon) Context() context.Context { return c.ctx } + +// StartTime returns the start time of the event. +func (c *ciVisibilityCommon) StartTime() time.Time { return c.startTime } + +// SetError sets an error on the event. +func (c *ciVisibilityCommon) SetError(err error) { + c.span.SetTag(ext.Error, err) +} + +// SetErrorInfo sets detailed error information on the event. +func (c *ciVisibilityCommon) SetErrorInfo(errType string, message string, callstack string) { + // set the span with error:1 + c.span.SetTag(ext.Error, true) + + // set the error type + if errType != "" { + c.span.SetTag(ext.ErrorType, errType) + } + + // set the error message + if message != "" { + c.span.SetTag(ext.ErrorMsg, message) + } + + // set the error stacktrace + if callstack != "" { + c.span.SetTag(ext.ErrorStack, callstack) + } +} + +// SetTag sets a tag on the event. +func (c *ciVisibilityCommon) SetTag(key string, value interface{}) { c.span.SetTag(key, value) } + +// fillCommonTags adds common tags to the span options for CI visibility. +func fillCommonTags(opts []tracer.StartSpanOption) []tracer.StartSpanOption { + opts = append(opts, []tracer.StartSpanOption{ + tracer.Tag(constants.Origin, constants.CIAppTestOrigin), + tracer.Tag(ext.ManualKeep, true), + }...) + + // Apply CI tags + for k, v := range utils.GetCITags() { + opts = append(opts, tracer.Tag(k, v)) + } + + return opts +} diff --git a/internal/civisibility/integrations/manual_api_ddtest.go b/internal/civisibility/integrations/manual_api_ddtest.go new file mode 100644 index 0000000000..cdb01f4d4b --- /dev/null +++ b/internal/civisibility/integrations/manual_api_ddtest.go @@ -0,0 +1,157 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package integrations + +import ( + "context" + "fmt" + "runtime" + "strings" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils" +) + +// Test + +// Ensures that tslvTest implements the DdTest interface. +var _ DdTest = (*tslvTest)(nil) + +// tslvTest implements the DdTest interface and represents an individual test within a suite. +type tslvTest struct { + ciVisibilityCommon + suite *tslvTestSuite + name string +} + +// createTest initializes a new test within a given suite. +func createTest(suite *tslvTestSuite, name string, startTime time.Time) DdTest { + if suite == nil { + return nil + } + + operationName := "test" + if suite.module.framework != "" { + operationName = fmt.Sprintf("%s.%s", strings.ToLower(suite.module.framework), operationName) + } + + resourceName := fmt.Sprintf("%s.%s", suite.name, name) + + // Test tags should include suite, module, and session tags so the backend can calculate the suite, module, and session fingerprint from the test. + testTags := append(suite.tags, tracer.Tag(constants.TestName, name)) + testOpts := append(fillCommonTags([]tracer.StartSpanOption{ + tracer.ResourceName(resourceName), + tracer.SpanType(constants.SpanTypeTest), + tracer.StartTime(startTime), + }), testTags...) + + span, ctx := tracer.StartSpanFromContext(context.Background(), operationName, testOpts...) + if suite.module.session != nil { + span.SetTag(constants.TestSessionIDTag, fmt.Sprint(suite.module.session.sessionID)) + } + span.SetTag(constants.TestModuleIDTag, fmt.Sprint(suite.module.moduleID)) + span.SetTag(constants.TestSuiteIDTag, fmt.Sprint(suite.suiteID)) + + t := &tslvTest{ + suite: suite, + name: name, + ciVisibilityCommon: ciVisibilityCommon{ + startTime: startTime, + tags: testTags, + span: span, + ctx: ctx, + }, + } + + // Ensure to close everything before CI visibility exits. In CI visibility mode, we try to never lose data. + PushCiVisibilityCloseAction(func() { t.Close(ResultStatusFail) }) + + return t +} + +// Name returns the name of the test. +func (t *tslvTest) Name() string { return t.name } + +// Suite returns the suite to which the test belongs. +func (t *tslvTest) Suite() DdTestSuite { return t.suite } + +// Close closes the test with the given status and sets the finish time to the current time. +func (t *tslvTest) Close(status TestResultStatus) { t.CloseWithFinishTime(status, time.Now()) } + +// CloseWithFinishTime closes the test with the given status and finish time. +func (t *tslvTest) CloseWithFinishTime(status TestResultStatus, finishTime time.Time) { + t.CloseWithFinishTimeAndSkipReason(status, finishTime, "") +} + +// CloseWithFinishTimeAndSkipReason closes the test with the given status, finish time, and skip reason. +func (t *tslvTest) CloseWithFinishTimeAndSkipReason(status TestResultStatus, finishTime time.Time, skipReason string) { + t.mutex.Lock() + defer t.mutex.Unlock() + if t.closed { + return + } + + switch status { + case ResultStatusPass: + t.span.SetTag(constants.TestStatus, constants.TestStatusPass) + case ResultStatusFail: + t.span.SetTag(constants.TestStatus, constants.TestStatusFail) + case ResultStatusSkip: + t.span.SetTag(constants.TestStatus, constants.TestStatusSkip) + } + + if skipReason != "" { + t.span.SetTag(constants.TestSkipReason, skipReason) + } + + t.span.Finish(tracer.FinishTime(finishTime)) + t.closed = true +} + +// SetError sets an error on the test and marks the suite and module as having an error. +func (t *tslvTest) SetError(err error) { + t.ciVisibilityCommon.SetError(err) + t.Suite().SetTag(ext.Error, true) + t.Suite().Module().SetTag(ext.Error, true) +} + +// SetErrorInfo sets detailed error information on the test and marks the suite and module as having an error. +func (t *tslvTest) SetErrorInfo(errType string, message string, callstack string) { + t.ciVisibilityCommon.SetErrorInfo(errType, message, callstack) + t.Suite().SetTag(ext.Error, true) + t.Suite().Module().SetTag(ext.Error, true) +} + +// SetTestFunc sets the function to be tested and records its source location. +func (t *tslvTest) SetTestFunc(fn *runtime.Func) { + if fn == nil { + return + } + + file, line := fn.FileLine(fn.Entry()) + file = utils.GetRelativePathFromCITagsSourceRoot(file) + t.SetTag(constants.TestSourceFile, file) + t.SetTag(constants.TestSourceStartLine, line) + + codeOwners := utils.GetCodeOwners() + if codeOwners != nil { + match, found := codeOwners.Match("/" + file) + if found { + t.SetTag(constants.TestCodeOwners, match.GetOwnersString()) + } + } +} + +// SetBenchmarkData sets benchmark data for the test. +func (t *tslvTest) SetBenchmarkData(measureType string, data map[string]any) { + t.span.SetTag(constants.TestType, constants.TestTypeBenchmark) + for k, v := range data { + t.span.SetTag(fmt.Sprintf("benchmark.%s.%s", measureType, k), v) + } +} diff --git a/internal/civisibility/integrations/manual_api_ddtestmodule.go b/internal/civisibility/integrations/manual_api_ddtestmodule.go new file mode 100644 index 0000000000..9f8746f4e3 --- /dev/null +++ b/internal/civisibility/integrations/manual_api_ddtestmodule.go @@ -0,0 +1,140 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package integrations + +import ( + "context" + "fmt" + "strings" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" +) + +// Test Module + +// Ensures that tslvTestModule implements the DdTestModule interface. +var _ DdTestModule = (*tslvTestModule)(nil) + +// tslvTestModule implements the DdTestModule interface and represents a module within a test session. +type tslvTestModule struct { + ciVisibilityCommon + session *tslvTestSession + moduleID uint64 + name string + framework string + + suites map[string]DdTestSuite +} + +// createTestModule initializes a new test module within a given session. +func createTestModule(session *tslvTestSession, name string, framework string, frameworkVersion string, startTime time.Time) DdTestModule { + // Ensure CI visibility is properly configured. + EnsureCiVisibilityInitialization() + + operationName := "test_module" + if framework != "" { + operationName = fmt.Sprintf("%s.%s", strings.ToLower(framework), operationName) + } + + resourceName := name + + var sessionTags []tracer.StartSpanOption + if session != nil { + sessionTags = session.tags + } + + // Module tags should include session tags so the backend can calculate the session fingerprint from the module. + moduleTags := append(sessionTags, []tracer.StartSpanOption{ + tracer.Tag(constants.TestType, constants.TestTypeTest), + tracer.Tag(constants.TestModule, name), + tracer.Tag(constants.TestFramework, framework), + tracer.Tag(constants.TestFrameworkVersion, frameworkVersion), + }...) + + testOpts := append(fillCommonTags([]tracer.StartSpanOption{ + tracer.ResourceName(resourceName), + tracer.SpanType(constants.SpanTypeTestModule), + tracer.StartTime(startTime), + }), moduleTags...) + + span, ctx := tracer.StartSpanFromContext(context.Background(), operationName, testOpts...) + moduleID := span.Context().SpanID() + if session != nil { + span.SetTag(constants.TestSessionIDTag, fmt.Sprint(session.sessionID)) + } + span.SetTag(constants.TestModuleIDTag, fmt.Sprint(moduleID)) + + module := &tslvTestModule{ + session: session, + moduleID: moduleID, + name: name, + framework: framework, + suites: map[string]DdTestSuite{}, + ciVisibilityCommon: ciVisibilityCommon{ + startTime: startTime, + tags: moduleTags, + span: span, + ctx: ctx, + }, + } + + // Ensure to close everything before CI visibility exits. In CI visibility mode, we try to never lose data. + PushCiVisibilityCloseAction(func() { module.Close() }) + + return module +} + +// Name returns the name of the test module. +func (t *tslvTestModule) Name() string { return t.name } + +// Framework returns the testing framework used by the test module. +func (t *tslvTestModule) Framework() string { return t.framework } + +// Session returns the test session to which the test module belongs. +func (t *tslvTestModule) Session() DdTestSession { return t.session } + +// Close closes the test module and sets the finish time to the current time. +func (t *tslvTestModule) Close() { t.CloseWithFinishTime(time.Now()) } + +// CloseWithFinishTime closes the test module with the given finish time. +func (t *tslvTestModule) CloseWithFinishTime(finishTime time.Time) { + t.mutex.Lock() + defer t.mutex.Unlock() + if t.closed { + return + } + + for _, suite := range t.suites { + suite.Close() + } + t.suites = map[string]DdTestSuite{} + + t.span.Finish(tracer.FinishTime(finishTime)) + t.closed = true +} + +// GetOrCreateSuite returns an existing suite or creates a new one with the given name. +func (t *tslvTestModule) GetOrCreateSuite(name string) DdTestSuite { + return t.GetOrCreateSuiteWithStartTime(name, time.Now()) +} + +// GetOrCreateSuiteWithStartTime returns an existing suite or creates a new one with the given name and start time. +func (t *tslvTestModule) GetOrCreateSuiteWithStartTime(name string, startTime time.Time) DdTestSuite { + t.mutex.Lock() + defer t.mutex.Unlock() + + var suite DdTestSuite + if v, ok := t.suites[name]; ok { + suite = v + } else { + suite = createTestSuite(t, name, startTime) + t.suites[name] = suite + } + + return suite +} diff --git a/internal/civisibility/integrations/manual_api_ddtestsession.go b/internal/civisibility/integrations/manual_api_ddtestsession.go new file mode 100644 index 0000000000..807cd835da --- /dev/null +++ b/internal/civisibility/integrations/manual_api_ddtestsession.go @@ -0,0 +1,170 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package integrations + +import ( + "context" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils" +) + +// Test Session + +// Ensures that tslvTestSession implements the DdTestSession interface. +var _ DdTestSession = (*tslvTestSession)(nil) + +// tslvTestSession implements the DdTestSession interface and represents a session for a set of tests. +type tslvTestSession struct { + ciVisibilityCommon + sessionID uint64 + command string + workingDirectory string + framework string + + modules map[string]DdTestModule +} + +// CreateTestSession initializes a new test session. It automatically determines the command and working directory. +func CreateTestSession() DdTestSession { + var cmd string + if len(os.Args) == 1 { + cmd = filepath.Base(os.Args[0]) + } else { + cmd = fmt.Sprintf("%s %s ", filepath.Base(os.Args[0]), strings.Join(os.Args[1:], " ")) + } + + // Filter out some parameters to make the command more stable. + cmd = regexp.MustCompile(`(?si)-test.gocoverdir=(.*)\s`).ReplaceAllString(cmd, "") + cmd = regexp.MustCompile(`(?si)-test.v=(.*)\s`).ReplaceAllString(cmd, "") + cmd = regexp.MustCompile(`(?si)-test.testlogfile=(.*)\s`).ReplaceAllString(cmd, "") + cmd = strings.TrimSpace(cmd) + wd, err := os.Getwd() + if err == nil { + wd = utils.GetRelativePathFromCITagsSourceRoot(wd) + } + return CreateTestSessionWith(cmd, wd, "", time.Now()) +} + +// CreateTestSessionWith initializes a new test session with specified command, working directory, framework, and start time. +func CreateTestSessionWith(command string, workingDirectory string, framework string, startTime time.Time) DdTestSession { + // Ensure CI visibility is properly configured. + EnsureCiVisibilityInitialization() + + operationName := "test_session" + if framework != "" { + operationName = fmt.Sprintf("%s.%s", strings.ToLower(framework), operationName) + } + + resourceName := fmt.Sprintf("%s.%s", operationName, command) + + sessionTags := []tracer.StartSpanOption{ + tracer.Tag(constants.TestType, constants.TestTypeTest), + tracer.Tag(constants.TestCommand, command), + tracer.Tag(constants.TestCommandWorkingDirectory, workingDirectory), + } + + testOpts := append(fillCommonTags([]tracer.StartSpanOption{ + tracer.ResourceName(resourceName), + tracer.SpanType(constants.SpanTypeTestSession), + tracer.StartTime(startTime), + }), sessionTags...) + + span, ctx := tracer.StartSpanFromContext(context.Background(), operationName, testOpts...) + sessionID := span.Context().SpanID() + span.SetTag(constants.TestSessionIDTag, fmt.Sprint(sessionID)) + + s := &tslvTestSession{ + sessionID: sessionID, + command: command, + workingDirectory: workingDirectory, + framework: framework, + modules: map[string]DdTestModule{}, + ciVisibilityCommon: ciVisibilityCommon{ + startTime: startTime, + tags: sessionTags, + span: span, + ctx: ctx, + }, + } + + // Ensure to close everything before CI visibility exits. In CI visibility mode, we try to never lose data. + PushCiVisibilityCloseAction(func() { s.Close(1) }) + + return s +} + +// Command returns the command used to run the test session. +func (t *tslvTestSession) Command() string { return t.command } + +// Framework returns the testing framework used in the test session. +func (t *tslvTestSession) Framework() string { return t.framework } + +// WorkingDirectory returns the working directory of the test session. +func (t *tslvTestSession) WorkingDirectory() string { return t.workingDirectory } + +// Close closes the test session with the given exit code and sets the finish time to the current time. +func (t *tslvTestSession) Close(exitCode int) { t.CloseWithFinishTime(exitCode, time.Now()) } + +// CloseWithFinishTime closes the test session with the given exit code and finish time. +func (t *tslvTestSession) CloseWithFinishTime(exitCode int, finishTime time.Time) { + t.mutex.Lock() + defer t.mutex.Unlock() + if t.closed { + return + } + + for _, m := range t.modules { + m.Close() + } + t.modules = map[string]DdTestModule{} + + t.span.SetTag(constants.TestCommandExitCode, exitCode) + if exitCode == 0 { + t.span.SetTag(constants.TestStatus, constants.TestStatusPass) + } else { + t.SetErrorInfo("ExitCode", "exit code is not zero.", "") + t.span.SetTag(constants.TestStatus, constants.TestStatusFail) + } + + t.span.Finish(tracer.FinishTime(finishTime)) + t.closed = true + + tracer.Flush() +} + +// GetOrCreateModule returns an existing module or creates a new one with the given name. +func (t *tslvTestSession) GetOrCreateModule(name string) DdTestModule { + return t.GetOrCreateModuleWithFramework(name, "", "") +} + +// GetOrCreateModuleWithFramework returns an existing module or creates a new one with the given name, framework, and framework version. +func (t *tslvTestSession) GetOrCreateModuleWithFramework(name string, framework string, frameworkVersion string) DdTestModule { + return t.GetOrCreateModuleWithFrameworkAndStartTime(name, framework, frameworkVersion, time.Now()) +} + +// GetOrCreateModuleWithFrameworkAndStartTime returns an existing module or creates a new one with the given name, framework, framework version, and start time. +func (t *tslvTestSession) GetOrCreateModuleWithFrameworkAndStartTime(name string, framework string, frameworkVersion string, startTime time.Time) DdTestModule { + t.mutex.Lock() + defer t.mutex.Unlock() + + var mod DdTestModule + if v, ok := t.modules[name]; ok { + mod = v + } else { + mod = createTestModule(t, name, framework, frameworkVersion, startTime) + t.modules[name] = mod + } + + return mod +} diff --git a/internal/civisibility/integrations/manual_api_ddtestsuite.go b/internal/civisibility/integrations/manual_api_ddtestsuite.go new file mode 100644 index 0000000000..9a1857dd21 --- /dev/null +++ b/internal/civisibility/integrations/manual_api_ddtestsuite.go @@ -0,0 +1,120 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package integrations + +import ( + "context" + "fmt" + "strings" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" +) + +// Test Suite + +// Ensures that tslvTestSuite implements the DdTestSuite interface. +var _ DdTestSuite = (*tslvTestSuite)(nil) + +// tslvTestSuite implements the DdTestSuite interface and represents a suite of tests within a module. +type tslvTestSuite struct { + ciVisibilityCommon + module *tslvTestModule + suiteID uint64 + name string +} + +// createTestSuite initializes a new test suite within a given module. +func createTestSuite(module *tslvTestModule, name string, startTime time.Time) DdTestSuite { + if module == nil { + return nil + } + + operationName := "test_suite" + if module.framework != "" { + operationName = fmt.Sprintf("%s.%s", strings.ToLower(module.framework), operationName) + } + + resourceName := name + + // Suite tags should include module and session tags so the backend can calculate the module and session fingerprint from the suite. + suiteTags := append(module.tags, tracer.Tag(constants.TestSuite, name)) + testOpts := append(fillCommonTags([]tracer.StartSpanOption{ + tracer.ResourceName(resourceName), + tracer.SpanType(constants.SpanTypeTestSuite), + tracer.StartTime(startTime), + }), suiteTags...) + + span, ctx := tracer.StartSpanFromContext(context.Background(), operationName, testOpts...) + suiteID := span.Context().SpanID() + if module.session != nil { + span.SetTag(constants.TestSessionIDTag, fmt.Sprint(module.session.sessionID)) + } + span.SetTag(constants.TestModuleIDTag, fmt.Sprint(module.moduleID)) + span.SetTag(constants.TestSuiteIDTag, fmt.Sprint(suiteID)) + + suite := &tslvTestSuite{ + module: module, + suiteID: suiteID, + name: name, + ciVisibilityCommon: ciVisibilityCommon{ + startTime: startTime, + tags: suiteTags, + span: span, + ctx: ctx, + }, + } + + // Ensure to close everything before CI visibility exits. In CI visibility mode, we try to never lose data. + PushCiVisibilityCloseAction(func() { suite.Close() }) + + return suite +} + +// Name returns the name of the test suite. +func (t *tslvTestSuite) Name() string { return t.name } + +// Module returns the module to which the test suite belongs. +func (t *tslvTestSuite) Module() DdTestModule { return t.module } + +// Close closes the test suite and sets the finish time to the current time. +func (t *tslvTestSuite) Close() { t.CloseWithFinishTime(time.Now()) } + +// CloseWithFinishTime closes the test suite with the given finish time. +func (t *tslvTestSuite) CloseWithFinishTime(finishTime time.Time) { + t.mutex.Lock() + defer t.mutex.Unlock() + if t.closed { + return + } + + t.span.Finish(tracer.FinishTime(finishTime)) + t.closed = true +} + +// SetError sets an error on the test suite and marks the module as having an error. +func (t *tslvTestSuite) SetError(err error) { + t.ciVisibilityCommon.SetError(err) + t.Module().SetTag(ext.Error, true) +} + +// SetErrorInfo sets detailed error information on the test suite and marks the module as having an error. +func (t *tslvTestSuite) SetErrorInfo(errType string, message string, callstack string) { + t.ciVisibilityCommon.SetErrorInfo(errType, message, callstack) + t.Module().SetTag(ext.Error, true) +} + +// CreateTest creates a new test with the given name and sets the start time to the current time. +func (t *tslvTestSuite) CreateTest(name string) DdTest { + return t.CreateTestWithStartTime(name, time.Now()) +} + +// CreateTestWithStartTime creates a new test with the given name and start time. +func (t *tslvTestSuite) CreateTestWithStartTime(name string, startTime time.Time) DdTest { + return createTest(t, name, startTime) +} diff --git a/internal/civisibility/integrations/manual_api_mocktracer_test.go b/internal/civisibility/integrations/manual_api_mocktracer_test.go new file mode 100644 index 0000000000..afb6d321e4 --- /dev/null +++ b/internal/civisibility/integrations/manual_api_mocktracer_test.go @@ -0,0 +1,277 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package integrations + +import ( + "errors" + "os" + "runtime" + "testing" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" + + "github.com/stretchr/testify/assert" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" +) + +var mockTracer mocktracer.Tracer + +func TestMain(m *testing.M) { + // Initialize civisibility using the mocktracer for testing + mockTracer = InitializeCIVisibilityMock() + + // Run tests + os.Exit(m.Run()) +} + +func createDDTestSession(now time.Time) DdTestSession { + session := CreateTestSessionWith("my-command", "/tmp/wd", "my-testing-framework", now) + session.SetTag("my-tag", "my-value") + return session +} + +func createDDTestModule(now time.Time) (DdTestSession, DdTestModule) { + session := createDDTestSession(now) + module := session.GetOrCreateModuleWithFrameworkAndStartTime("my-module", "my-module-framework", "framework-version", now) + module.SetTag("my-tag", "my-value") + return session, module +} + +func createDDTestSuite(now time.Time) (DdTestSession, DdTestModule, DdTestSuite) { + session, module := createDDTestModule(now) + suite := module.GetOrCreateSuiteWithStartTime("my-suite", now) + suite.SetTag("my-tag", "my-value") + return session, module, suite +} + +func createDDTest(now time.Time) (DdTestSession, DdTestModule, DdTestSuite, DdTest) { + session, module, suite := createDDTestSuite(now) + test := suite.CreateTestWithStartTime("my-test", now) + test.SetTag("my-tag", "my-value") + return session, module, suite, test +} + +func commonAssertions(assert *assert.Assertions, sessionSpan mocktracer.Span) { + tags := map[string]interface{}{ + "my-tag": "my-value", + constants.Origin: constants.CIAppTestOrigin, + constants.TestType: constants.TestTypeTest, + constants.TestCommand: "my-command", + } + + spanTags := sessionSpan.Tags() + + assert.Subset(spanTags, tags) + assert.Contains(spanTags, constants.OSPlatform) + assert.Contains(spanTags, constants.OSArchitecture) + assert.Contains(spanTags, constants.OSVersion) + assert.Contains(spanTags, constants.RuntimeVersion) + assert.Contains(spanTags, constants.RuntimeName) + assert.Contains(spanTags, constants.GitRepositoryURL) + assert.Contains(spanTags, constants.GitCommitSHA) +} + +func TestSession(t *testing.T) { + mockTracer.Reset() + assert := assert.New(t) + + now := time.Now() + session := createDDTestSession(now) + assert.NotNil(session.Context()) + assert.Equal("my-command", session.Command()) + assert.Equal("/tmp/wd", session.WorkingDirectory()) + assert.Equal("my-testing-framework", session.Framework()) + assert.Equal(now, session.StartTime()) + + session.Close(42) + + finishedSpans := mockTracer.FinishedSpans() + assert.Equal(1, len(finishedSpans)) + sessionAssertions(assert, now, finishedSpans[0]) + + // session already closed, this is a no-op + session.Close(0) +} + +func sessionAssertions(assert *assert.Assertions, now time.Time, sessionSpan mocktracer.Span) { + assert.Equal(now, sessionSpan.StartTime()) + assert.Equal("my-testing-framework.test_session", sessionSpan.OperationName()) + + tags := map[string]interface{}{ + ext.ResourceName: "my-testing-framework.test_session.my-command", + ext.Error: true, + ext.ErrorType: "ExitCode", + ext.ErrorMsg: "exit code is not zero.", + ext.SpanType: constants.SpanTypeTestSession, + constants.TestStatus: constants.TestStatusFail, + constants.TestCommandExitCode: 42, + } + + spanTags := sessionSpan.Tags() + + assert.Subset(spanTags, tags) + assert.Contains(spanTags, constants.TestSessionIDTag) + commonAssertions(assert, sessionSpan) +} + +func TestModule(t *testing.T) { + mockTracer.Reset() + assert := assert.New(t) + + now := time.Now() + session, module := createDDTestModule(now) + defer func() { session.Close(0) }() + module.SetErrorInfo("my-type", "my-message", "my-stack") + + assert.NotNil(module.Context()) + assert.Equal("my-module", module.Name()) + assert.Equal("my-module-framework", module.Framework()) + assert.Equal(now, module.StartTime()) + assert.Equal(session, module.Session()) + + module.Close() + + finishedSpans := mockTracer.FinishedSpans() + assert.Equal(1, len(finishedSpans)) + moduleAssertions(assert, now, finishedSpans[0]) + + //no-op call + module.Close() +} + +func moduleAssertions(assert *assert.Assertions, now time.Time, moduleSpan mocktracer.Span) { + assert.Equal(now, moduleSpan.StartTime()) + assert.Equal("my-module-framework.test_module", moduleSpan.OperationName()) + + tags := map[string]interface{}{ + ext.ResourceName: "my-module", + ext.Error: true, + ext.ErrorType: "my-type", + ext.ErrorMsg: "my-message", + ext.ErrorStack: "my-stack", + ext.SpanType: constants.SpanTypeTestModule, + constants.TestModule: "my-module", + } + + spanTags := moduleSpan.Tags() + + assert.Subset(spanTags, tags) + assert.Contains(spanTags, constants.TestSessionIDTag) + assert.Contains(spanTags, constants.TestModuleIDTag) + commonAssertions(assert, moduleSpan) +} + +func TestSuite(t *testing.T) { + mockTracer.Reset() + assert := assert.New(t) + + now := time.Now() + session, module, suite := createDDTestSuite(now) + defer func() { + session.Close(0) + module.Close() + }() + suite.SetErrorInfo("my-type", "my-message", "my-stack") + + assert.NotNil(suite.Context()) + assert.Equal("my-suite", suite.Name()) + assert.Equal(now, suite.StartTime()) + assert.Equal(module, suite.Module()) + + suite.Close() + + finishedSpans := mockTracer.FinishedSpans() + assert.Equal(1, len(finishedSpans)) + suiteAssertions(assert, now, finishedSpans[0]) + + //no-op call + suite.Close() +} + +func suiteAssertions(assert *assert.Assertions, now time.Time, suiteSpan mocktracer.Span) { + assert.Equal(now, suiteSpan.StartTime()) + assert.Equal("my-module-framework.test_suite", suiteSpan.OperationName()) + + tags := map[string]interface{}{ + ext.ResourceName: "my-suite", + ext.Error: true, + ext.ErrorType: "my-type", + ext.ErrorMsg: "my-message", + ext.ErrorStack: "my-stack", + ext.SpanType: constants.SpanTypeTestSuite, + constants.TestModule: "my-module", + constants.TestSuite: "my-suite", + } + + spanTags := suiteSpan.Tags() + + assert.Subset(spanTags, tags) + assert.Contains(spanTags, constants.TestSessionIDTag) + assert.Contains(spanTags, constants.TestModuleIDTag) + assert.Contains(spanTags, constants.TestSuiteIDTag) + commonAssertions(assert, suiteSpan) +} + +func Test(t *testing.T) { + mockTracer.Reset() + assert := assert.New(t) + + now := time.Now() + session, module, suite, test := createDDTest(now) + defer func() { + session.Close(0) + module.Close() + suite.Close() + }() + test.SetError(errors.New("we keep the last error")) + test.SetErrorInfo("my-type", "my-message", "my-stack") + pc, _, _, _ := runtime.Caller(0) + test.SetTestFunc(runtime.FuncForPC(pc)) + + assert.NotNil(test.Context()) + assert.Equal("my-test", test.Name()) + assert.Equal(now, test.StartTime()) + assert.Equal(suite, test.Suite()) + + test.Close(ResultStatusPass) + + finishedSpans := mockTracer.FinishedSpans() + assert.Equal(1, len(finishedSpans)) + testAssertions(assert, now, finishedSpans[0]) + + //no-op call + test.Close(ResultStatusSkip) +} + +func testAssertions(assert *assert.Assertions, now time.Time, testSpan mocktracer.Span) { + assert.Equal(now, testSpan.StartTime()) + assert.Equal("my-module-framework.test", testSpan.OperationName()) + + tags := map[string]interface{}{ + ext.ResourceName: "my-suite.my-test", + ext.Error: true, + ext.ErrorType: "my-type", + ext.ErrorMsg: "my-message", + ext.ErrorStack: "my-stack", + ext.SpanType: constants.SpanTypeTest, + constants.TestModule: "my-module", + constants.TestSuite: "my-suite", + constants.TestName: "my-test", + constants.TestStatus: constants.TestStatusPass, + } + + spanTags := testSpan.Tags() + + assert.Subset(spanTags, tags) + assert.Contains(spanTags, constants.TestSessionIDTag) + assert.Contains(spanTags, constants.TestModuleIDTag) + assert.Contains(spanTags, constants.TestSuiteIDTag) + assert.Contains(spanTags, constants.TestSourceFile) + assert.Contains(spanTags, constants.TestSourceStartLine) + commonAssertions(assert, testSpan) +} diff --git a/internal/civisibility/integrations/manual_api_test.go b/internal/civisibility/integrations/manual_api_test.go new file mode 100644 index 0000000000..2bb0a4ecb3 --- /dev/null +++ b/internal/civisibility/integrations/manual_api_test.go @@ -0,0 +1,329 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package integrations + +import ( + "context" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// Mocking the ddTslvEvent interface +type MockDdTslvEvent struct { + mock.Mock +} + +func (m *MockDdTslvEvent) Context() context.Context { + args := m.Called() + return args.Get(0).(context.Context) +} + +func (m *MockDdTslvEvent) StartTime() time.Time { + args := m.Called() + return args.Get(0).(time.Time) +} + +func (m *MockDdTslvEvent) SetError(err error) { + m.Called(err) +} + +func (m *MockDdTslvEvent) SetErrorInfo(errType string, message string, callstack string) { + m.Called(errType, message, callstack) +} + +func (m *MockDdTslvEvent) SetTag(key string, value interface{}) { + m.Called(key, value) +} + +// Mocking the DdTest interface +type MockDdTest struct { + MockDdTslvEvent + mock.Mock +} + +func (m *MockDdTest) Name() string { + args := m.Called() + return args.String(0) +} + +func (m *MockDdTest) Suite() DdTestSuite { + args := m.Called() + return args.Get(0).(DdTestSuite) +} + +func (m *MockDdTest) Close(status TestResultStatus) { + m.Called(status) +} + +func (m *MockDdTest) CloseWithFinishTime(status TestResultStatus, finishTime time.Time) { + m.Called(status, finishTime) +} + +func (m *MockDdTest) CloseWithFinishTimeAndSkipReason(status TestResultStatus, finishTime time.Time, skipReason string) { + m.Called(status, finishTime, skipReason) +} + +func (m *MockDdTest) SetTestFunc(fn *runtime.Func) { + m.Called(fn) +} + +func (m *MockDdTest) SetBenchmarkData(measureType string, data map[string]any) { + m.Called(measureType, data) +} + +// Mocking the DdTestSession interface +type MockDdTestSession struct { + MockDdTslvEvent + mock.Mock +} + +func (m *MockDdTestSession) Command() string { + args := m.Called() + return args.String(0) +} + +func (m *MockDdTestSession) Framework() string { + args := m.Called() + return args.String(0) +} + +func (m *MockDdTestSession) WorkingDirectory() string { + args := m.Called() + return args.String(0) +} + +func (m *MockDdTestSession) Close(exitCode int) { + m.Called(exitCode) +} + +func (m *MockDdTestSession) CloseWithFinishTime(exitCode int, finishTime time.Time) { + m.Called(exitCode, finishTime) +} + +func (m *MockDdTestSession) GetOrCreateModule(name string) DdTestModule { + args := m.Called(name) + return args.Get(0).(DdTestModule) +} + +func (m *MockDdTestSession) GetOrCreateModuleWithFramework(name string, framework string, frameworkVersion string) DdTestModule { + args := m.Called(name, framework, frameworkVersion) + return args.Get(0).(DdTestModule) +} + +func (m *MockDdTestSession) GetOrCreateModuleWithFrameworkAndStartTime(name string, framework string, frameworkVersion string, startTime time.Time) DdTestModule { + args := m.Called(name, framework, frameworkVersion, startTime) + return args.Get(0).(DdTestModule) +} + +// Mocking the DdTestModule interface +type MockDdTestModule struct { + MockDdTslvEvent + mock.Mock +} + +func (m *MockDdTestModule) Session() DdTestSession { + args := m.Called() + return args.Get(0).(DdTestSession) +} + +func (m *MockDdTestModule) Framework() string { + args := m.Called() + return args.String(0) +} + +func (m *MockDdTestModule) Name() string { + args := m.Called() + return args.String(0) +} + +func (m *MockDdTestModule) Close() { + m.Called() +} + +func (m *MockDdTestModule) CloseWithFinishTime(finishTime time.Time) { + m.Called(finishTime) +} + +func (m *MockDdTestModule) GetOrCreateSuite(name string) DdTestSuite { + args := m.Called(name) + return args.Get(0).(DdTestSuite) +} + +func (m *MockDdTestModule) GetOrCreateSuiteWithStartTime(name string, startTime time.Time) DdTestSuite { + args := m.Called(name, startTime) + return args.Get(0).(DdTestSuite) +} + +// Mocking the DdTestSuite interface +type MockDdTestSuite struct { + MockDdTslvEvent + mock.Mock +} + +func (m *MockDdTestSuite) Module() DdTestModule { + args := m.Called() + return args.Get(0).(DdTestModule) +} + +func (m *MockDdTestSuite) Name() string { + args := m.Called() + return args.String(0) +} + +func (m *MockDdTestSuite) Close() { + m.Called() +} + +func (m *MockDdTestSuite) CloseWithFinishTime(finishTime time.Time) { + m.Called(finishTime) +} + +func (m *MockDdTestSuite) CreateTest(name string) DdTest { + args := m.Called(name) + return args.Get(0).(DdTest) +} + +func (m *MockDdTestSuite) CreateTestWithStartTime(name string, startTime time.Time) DdTest { + args := m.Called(name, startTime) + return args.Get(0).(DdTest) +} + +// Unit tests +func TestDdTestSession(t *testing.T) { + mockSession := new(MockDdTestSession) + mockSession.On("Command").Return("test-command") + mockSession.On("Framework").Return("test-framework") + mockSession.On("WorkingDirectory").Return("/path/to/working/dir") + mockSession.On("Close", 0).Return() + mockSession.On("CloseWithFinishTime", 0, mock.Anything).Return() + mockSession.On("GetOrCreateModule", "test-module").Return(new(MockDdTestModule)) + mockSession.On("GetOrCreateModuleWithFramework", "test-module", "test-framework", "1.0").Return(new(MockDdTestModule)) + mockSession.On("GetOrCreateModuleWithFrameworkAndStartTime", "test-module", "test-framework", "1.0", mock.Anything).Return(new(MockDdTestModule)) + + session := (DdTestSession)(mockSession) + assert.Equal(t, "test-command", session.Command()) + assert.Equal(t, "test-framework", session.Framework()) + assert.Equal(t, "/path/to/working/dir", session.WorkingDirectory()) + + session.Close(0) + mockSession.AssertCalled(t, "Close", 0) + + now := time.Now() + session.CloseWithFinishTime(0, now) + mockSession.AssertCalled(t, "CloseWithFinishTime", 0, now) + + module := session.GetOrCreateModule("test-module") + assert.NotNil(t, module) + mockSession.AssertCalled(t, "GetOrCreateModule", "test-module") + + module = session.GetOrCreateModuleWithFramework("test-module", "test-framework", "1.0") + assert.NotNil(t, module) + mockSession.AssertCalled(t, "GetOrCreateModuleWithFramework", "test-module", "test-framework", "1.0") + + module = session.GetOrCreateModuleWithFrameworkAndStartTime("test-module", "test-framework", "1.0", now) + assert.NotNil(t, module) + mockSession.AssertCalled(t, "GetOrCreateModuleWithFrameworkAndStartTime", "test-module", "test-framework", "1.0", now) +} + +func TestDdTestModule(t *testing.T) { + mockModule := new(MockDdTestModule) + mockModule.On("Session").Return(new(MockDdTestSession)) + mockModule.On("Framework").Return("test-framework") + mockModule.On("Name").Return("test-module") + mockModule.On("Close").Return() + mockModule.On("CloseWithFinishTime", mock.Anything).Return() + mockModule.On("GetOrCreateSuite", "test-suite").Return(new(MockDdTestSuite)) + mockModule.On("GetOrCreateSuiteWithStartTime", "test-suite", mock.Anything).Return(new(MockDdTestSuite)) + + module := (DdTestModule)(mockModule) + + assert.Equal(t, "test-framework", module.Framework()) + assert.Equal(t, "test-module", module.Name()) + + module.Close() + mockModule.AssertCalled(t, "Close") + + now := time.Now() + module.CloseWithFinishTime(now) + mockModule.AssertCalled(t, "CloseWithFinishTime", now) + + suite := module.GetOrCreateSuite("test-suite") + assert.NotNil(t, suite) + mockModule.AssertCalled(t, "GetOrCreateSuite", "test-suite") + + suite = module.GetOrCreateSuiteWithStartTime("test-suite", now) + assert.NotNil(t, suite) + mockModule.AssertCalled(t, "GetOrCreateSuiteWithStartTime", "test-suite", now) +} + +func TestDdTestSuite(t *testing.T) { + mockSuite := new(MockDdTestSuite) + mockSuite.On("Module").Return(new(MockDdTestModule)) + mockSuite.On("Name").Return("test-suite") + mockSuite.On("Close").Return() + mockSuite.On("CloseWithFinishTime", mock.Anything).Return() + mockSuite.On("CreateTest", "test-name").Return(new(MockDdTest)) + mockSuite.On("CreateTestWithStartTime", "test-name", mock.Anything).Return(new(MockDdTest)) + + suite := (DdTestSuite)(mockSuite) + + assert.Equal(t, "test-suite", suite.Name()) + + suite.Close() + mockSuite.AssertCalled(t, "Close") + + now := time.Now() + suite.CloseWithFinishTime(now) + mockSuite.AssertCalled(t, "CloseWithFinishTime", now) + + test := suite.CreateTest("test-name") + assert.NotNil(t, test) + mockSuite.AssertCalled(t, "CreateTest", "test-name") + + test = suite.CreateTestWithStartTime("test-name", now) + assert.NotNil(t, test) + mockSuite.AssertCalled(t, "CreateTestWithStartTime", "test-name", now) +} + +func TestDdTest(t *testing.T) { + mockTest := new(MockDdTest) + mockTest.On("Name").Return("test-name") + mockTest.On("Suite").Return(new(MockDdTestSuite)) + mockTest.On("Close", ResultStatusPass).Return() + mockTest.On("CloseWithFinishTime", ResultStatusPass, mock.Anything).Return() + mockTest.On("CloseWithFinishTimeAndSkipReason", ResultStatusSkip, mock.Anything, "SkipReason").Return() + mockTest.On("SetTestFunc", mock.Anything).Return() + mockTest.On("SetBenchmarkData", "measure-type", mock.Anything).Return() + + test := (DdTest)(mockTest) + + assert.Equal(t, "test-name", test.Name()) + + suite := test.Suite() + assert.NotNil(t, suite) + + test.Close(ResultStatusPass) + mockTest.AssertCalled(t, "Close", ResultStatusPass) + + now := time.Now() + test.CloseWithFinishTime(ResultStatusPass, now) + mockTest.AssertCalled(t, "CloseWithFinishTime", ResultStatusPass, now) + + skipReason := "SkipReason" + test.CloseWithFinishTimeAndSkipReason(ResultStatusSkip, now, skipReason) + mockTest.AssertCalled(t, "CloseWithFinishTimeAndSkipReason", ResultStatusSkip, now, skipReason) + + test.SetTestFunc(nil) + mockTest.AssertCalled(t, "SetTestFunc", (*runtime.Func)(nil)) + + benchmarkData := map[string]any{"key": "value"} + test.SetBenchmarkData("measure-type", benchmarkData) + mockTest.AssertCalled(t, "SetBenchmarkData", "measure-type", benchmarkData) +} diff --git a/internal/civisibility/utils/ci_providers.go b/internal/civisibility/utils/ci_providers.go new file mode 100644 index 0000000000..bb8b6940d5 --- /dev/null +++ b/internal/civisibility/utils/ci_providers.go @@ -0,0 +1,569 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "encoding/json" + "fmt" + "os" + "regexp" + "sort" + "strings" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" +) + +// providerType defines a function type that returns a map of string key-value pairs. +type providerType = func() map[string]string + +// providers maps environment variable names to their corresponding CI provider extraction functions. +var providers = map[string]providerType{ + "APPVEYOR": extractAppveyor, + "TF_BUILD": extractAzurePipelines, + "BITBUCKET_COMMIT": extractBitbucket, + "BUDDY": extractBuddy, + "BUILDKITE": extractBuildkite, + "CIRCLECI": extractCircleCI, + "GITHUB_SHA": extractGithubActions, + "GITLAB_CI": extractGitlab, + "JENKINS_URL": extractJenkins, + "TEAMCITY_VERSION": extractTeamcity, + "TRAVIS": extractTravis, + "BITRISE_BUILD_SLUG": extractBitrise, + "CF_BUILD_ID": extractCodefresh, + "CODEBUILD_INITIATOR": extractAwsCodePipeline, +} + +// getEnvVarsJSON returns a JSON representation of the specified environment variables. +func getEnvVarsJSON(envVars ...string) ([]byte, error) { + envVarsMap := make(map[string]string) + for _, envVar := range envVars { + value := os.Getenv(envVar) + if value != "" { + envVarsMap[envVar] = value + } + } + return json.Marshal(envVarsMap) +} + +// getProviderTags extracts CI information from environment variables. +func getProviderTags() map[string]string { + tags := map[string]string{} + for key, provider := range providers { + if _, ok := os.LookupEnv(key); !ok { + continue + } + tags = provider() + } + + // replace with user specific tags + replaceWithUserSpecificTags(tags) + + // Normalize tags + normalizeTags(tags) + + // Expand ~ + if tag, ok := tags[constants.CIWorkspacePath]; ok && tag != "" { + tags[constants.CIWorkspacePath] = ExpandPath(tag) + } + + // remove empty values + for tag, value := range tags { + if value == "" { + delete(tags, tag) + } + } + + return tags +} + +// normalizeTags normalizes specific tags to remove prefixes and sensitive information. +func normalizeTags(tags map[string]string) { + if tag, ok := tags[constants.GitBranch]; ok && tag != "" { + if strings.Contains(tag, "refs/tags") || strings.Contains(tag, "origin/tags") || strings.Contains(tag, "refs/heads/tags") { + tags[constants.GitTag] = normalizeRef(tag) + } + tags[constants.GitBranch] = normalizeRef(tag) + } + if tag, ok := tags[constants.GitTag]; ok && tag != "" { + tags[constants.GitTag] = normalizeRef(tag) + } + if tag, ok := tags[constants.GitRepositoryURL]; ok && tag != "" { + tags[constants.GitRepositoryURL] = filterSensitiveInfo(tag) + } + if tag, ok := tags[constants.CIPipelineURL]; ok && tag != "" { + tags[constants.CIPipelineURL] = filterSensitiveInfo(tag) + } + if tag, ok := tags[constants.CIJobURL]; ok && tag != "" { + tags[constants.CIJobURL] = filterSensitiveInfo(tag) + } + if tag, ok := tags[constants.CIEnvVars]; ok && tag != "" { + tags[constants.CIEnvVars] = filterSensitiveInfo(tag) + } +} + +// replaceWithUserSpecificTags replaces certain tags with user-specific environment variable values. +func replaceWithUserSpecificTags(tags map[string]string) { + replace := func(tagName, envName string) { + tags[tagName] = getEnvironmentVariableIfIsNotEmpty(envName, tags[tagName]) + } + + replace(constants.GitBranch, "DD_GIT_BRANCH") + replace(constants.GitTag, "DD_GIT_TAG") + replace(constants.GitRepositoryURL, "DD_GIT_REPOSITORY_URL") + replace(constants.GitCommitSHA, "DD_GIT_COMMIT_SHA") + replace(constants.GitCommitMessage, "DD_GIT_COMMIT_MESSAGE") + replace(constants.GitCommitAuthorName, "DD_GIT_COMMIT_AUTHOR_NAME") + replace(constants.GitCommitAuthorEmail, "DD_GIT_COMMIT_AUTHOR_EMAIL") + replace(constants.GitCommitAuthorDate, "DD_GIT_COMMIT_AUTHOR_DATE") + replace(constants.GitCommitCommitterName, "DD_GIT_COMMIT_COMMITTER_NAME") + replace(constants.GitCommitCommitterEmail, "DD_GIT_COMMIT_COMMITTER_EMAIL") + replace(constants.GitCommitCommitterDate, "DD_GIT_COMMIT_COMMITTER_DATE") +} + +// getEnvironmentVariableIfIsNotEmpty returns the environment variable value if it is not empty, otherwise returns the default value. +func getEnvironmentVariableIfIsNotEmpty(key string, defaultValue string) string { + if value, ok := os.LookupEnv(key); ok && value != "" { + return value + } + return defaultValue +} + +// normalizeRef normalizes a Git reference name by removing common prefixes. +func normalizeRef(name string) string { + // Define the prefixes to remove + prefixes := []string{"refs/heads/", "refs/", "origin/", "tags/"} + + // Iterate over prefixes and remove them if present + for _, prefix := range prefixes { + if strings.HasPrefix(name, prefix) { + name = strings.TrimPrefix(name, prefix) + } + } + return name +} + +// firstEnv returns the value of the first non-empty environment variable from the provided list. +func firstEnv(keys ...string) string { + for _, key := range keys { + if value, ok := os.LookupEnv(key); ok { + if value != "" { + return value + } + } + } + return "" +} + +// extractAppveyor extracts CI information specific to Appveyor. +func extractAppveyor() map[string]string { + tags := map[string]string{} + url := fmt.Sprintf("https://ci.appveyor.com/project/%s/builds/%s", os.Getenv("APPVEYOR_REPO_NAME"), os.Getenv("APPVEYOR_BUILD_ID")) + tags[constants.CIProviderName] = "appveyor" + if os.Getenv("APPVEYOR_REPO_PROVIDER") == "github" { + tags[constants.GitRepositoryURL] = fmt.Sprintf("https://github.com/%s.git", os.Getenv("APPVEYOR_REPO_NAME")) + } else { + tags[constants.GitRepositoryURL] = os.Getenv("APPVEYOR_REPO_NAME") + } + + tags[constants.GitCommitSHA] = os.Getenv("APPVEYOR_REPO_COMMIT") + tags[constants.GitBranch] = firstEnv("APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH", "APPVEYOR_REPO_BRANCH") + tags[constants.GitTag] = os.Getenv("APPVEYOR_REPO_TAG_NAME") + + tags[constants.CIWorkspacePath] = os.Getenv("APPVEYOR_BUILD_FOLDER") + tags[constants.CIPipelineID] = os.Getenv("APPVEYOR_BUILD_ID") + tags[constants.CIPipelineName] = os.Getenv("APPVEYOR_REPO_NAME") + tags[constants.CIPipelineNumber] = os.Getenv("APPVEYOR_BUILD_NUMBER") + tags[constants.CIPipelineURL] = url + tags[constants.CIJobURL] = url + tags[constants.GitCommitMessage] = fmt.Sprintf("%s\n%s", os.Getenv("APPVEYOR_REPO_COMMIT_MESSAGE"), os.Getenv("APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED")) + tags[constants.GitCommitAuthorName] = os.Getenv("APPVEYOR_REPO_COMMIT_AUTHOR") + tags[constants.GitCommitAuthorEmail] = os.Getenv("APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL") + return tags +} + +// extractAzurePipelines extracts CI information specific to Azure Pipelines. +func extractAzurePipelines() map[string]string { + tags := map[string]string{} + baseURL := fmt.Sprintf("%s%s/_build/results?buildId=%s", os.Getenv("SYSTEM_TEAMFOUNDATIONSERVERURI"), os.Getenv("SYSTEM_TEAMPROJECTID"), os.Getenv("BUILD_BUILDID")) + pipelineURL := baseURL + jobURL := fmt.Sprintf("%s&view=logs&j=%s&t=%s", baseURL, os.Getenv("SYSTEM_JOBID"), os.Getenv("SYSTEM_TASKINSTANCEID")) + branchOrTag := firstEnv("SYSTEM_PULLREQUEST_SOURCEBRANCH", "BUILD_SOURCEBRANCH", "BUILD_SOURCEBRANCHNAME") + branch := "" + tag := "" + if strings.Contains(branchOrTag, "tags/") { + tag = branchOrTag + } else { + branch = branchOrTag + } + tags[constants.CIProviderName] = "azurepipelines" + tags[constants.CIWorkspacePath] = os.Getenv("BUILD_SOURCESDIRECTORY") + + tags[constants.CIPipelineID] = os.Getenv("BUILD_BUILDID") + tags[constants.CIPipelineName] = os.Getenv("BUILD_DEFINITIONNAME") + tags[constants.CIPipelineNumber] = os.Getenv("BUILD_BUILDID") + tags[constants.CIPipelineURL] = pipelineURL + + tags[constants.CIStageName] = os.Getenv("SYSTEM_STAGEDISPLAYNAME") + + tags[constants.CIJobName] = os.Getenv("SYSTEM_JOBDISPLAYNAME") + tags[constants.CIJobURL] = jobURL + + tags[constants.GitRepositoryURL] = firstEnv("SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", "BUILD_REPOSITORY_URI") + tags[constants.GitCommitSHA] = firstEnv("SYSTEM_PULLREQUEST_SOURCECOMMITID", "BUILD_SOURCEVERSION") + tags[constants.GitBranch] = branch + tags[constants.GitTag] = tag + tags[constants.GitCommitMessage] = os.Getenv("BUILD_SOURCEVERSIONMESSAGE") + tags[constants.GitCommitAuthorName] = os.Getenv("BUILD_REQUESTEDFORID") + tags[constants.GitCommitAuthorEmail] = os.Getenv("BUILD_REQUESTEDFOREMAIL") + + jsonString, err := getEnvVarsJSON("SYSTEM_TEAMPROJECTID", "BUILD_BUILDID", "SYSTEM_JOBID") + if err == nil { + tags[constants.CIEnvVars] = string(jsonString) + } + + return tags +} + +// extractBitrise extracts CI information specific to Bitrise. +func extractBitrise() map[string]string { + tags := map[string]string{} + tags[constants.CIProviderName] = "bitrise" + tags[constants.GitRepositoryURL] = os.Getenv("GIT_REPOSITORY_URL") + tags[constants.GitCommitSHA] = firstEnv("BITRISE_GIT_COMMIT", "GIT_CLONE_COMMIT_HASH") + tags[constants.GitBranch] = firstEnv("BITRISEIO_GIT_BRANCH_DEST", "BITRISE_GIT_BRANCH") + tags[constants.GitTag] = os.Getenv("BITRISE_GIT_TAG") + tags[constants.CIWorkspacePath] = os.Getenv("BITRISE_SOURCE_DIR") + tags[constants.CIPipelineID] = os.Getenv("BITRISE_BUILD_SLUG") + tags[constants.CIPipelineName] = os.Getenv("BITRISE_TRIGGERED_WORKFLOW_ID") + tags[constants.CIPipelineNumber] = os.Getenv("BITRISE_BUILD_NUMBER") + tags[constants.CIPipelineURL] = os.Getenv("BITRISE_BUILD_URL") + tags[constants.GitCommitMessage] = os.Getenv("BITRISE_GIT_MESSAGE") + return tags +} + +// extractBitbucket extracts CI information specific to Bitbucket. +func extractBitbucket() map[string]string { + tags := map[string]string{} + url := fmt.Sprintf("https://bitbucket.org/%s/addon/pipelines/home#!/results/%s", os.Getenv("BITBUCKET_REPO_FULL_NAME"), os.Getenv("BITBUCKET_BUILD_NUMBER")) + tags[constants.CIProviderName] = "bitbucket" + tags[constants.GitRepositoryURL] = firstEnv("BITBUCKET_GIT_SSH_ORIGIN", "BITBUCKET_GIT_HTTP_ORIGIN") + tags[constants.GitCommitSHA] = os.Getenv("BITBUCKET_COMMIT") + tags[constants.GitBranch] = os.Getenv("BITBUCKET_BRANCH") + tags[constants.GitTag] = os.Getenv("BITBUCKET_TAG") + tags[constants.CIWorkspacePath] = os.Getenv("BITBUCKET_CLONE_DIR") + tags[constants.CIPipelineID] = strings.Trim(os.Getenv("BITBUCKET_PIPELINE_UUID"), "{}") + tags[constants.CIPipelineNumber] = os.Getenv("BITBUCKET_BUILD_NUMBER") + tags[constants.CIPipelineName] = os.Getenv("BITBUCKET_REPO_FULL_NAME") + tags[constants.CIPipelineURL] = url + tags[constants.CIJobURL] = url + return tags +} + +// extractBuddy extracts CI information specific to Buddy. +func extractBuddy() map[string]string { + tags := map[string]string{} + tags[constants.CIProviderName] = "buddy" + tags[constants.CIPipelineID] = fmt.Sprintf("%s/%s", os.Getenv("BUDDY_PIPELINE_ID"), os.Getenv("BUDDY_EXECUTION_ID")) + tags[constants.CIPipelineName] = os.Getenv("BUDDY_PIPELINE_NAME") + tags[constants.CIPipelineNumber] = os.Getenv("BUDDY_EXECUTION_ID") + tags[constants.CIPipelineURL] = os.Getenv("BUDDY_EXECUTION_URL") + tags[constants.GitCommitSHA] = os.Getenv("BUDDY_EXECUTION_REVISION") + tags[constants.GitRepositoryURL] = os.Getenv("BUDDY_SCM_URL") + tags[constants.GitBranch] = os.Getenv("BUDDY_EXECUTION_BRANCH") + tags[constants.GitTag] = os.Getenv("BUDDY_EXECUTION_TAG") + tags[constants.GitCommitMessage] = os.Getenv("BUDDY_EXECUTION_REVISION_MESSAGE") + tags[constants.GitCommitCommitterName] = os.Getenv("BUDDY_EXECUTION_REVISION_COMMITTER_NAME") + tags[constants.GitCommitCommitterEmail] = os.Getenv("BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL") + return tags +} + +// extractBuildkite extracts CI information specific to Buildkite. +func extractBuildkite() map[string]string { + tags := map[string]string{} + tags[constants.GitBranch] = os.Getenv("BUILDKITE_BRANCH") + tags[constants.GitCommitSHA] = os.Getenv("BUILDKITE_COMMIT") + tags[constants.GitRepositoryURL] = os.Getenv("BUILDKITE_REPO") + tags[constants.GitTag] = os.Getenv("BUILDKITE_TAG") + tags[constants.CIPipelineID] = os.Getenv("BUILDKITE_BUILD_ID") + tags[constants.CIPipelineName] = os.Getenv("BUILDKITE_PIPELINE_SLUG") + tags[constants.CIPipelineNumber] = os.Getenv("BUILDKITE_BUILD_NUMBER") + tags[constants.CIPipelineURL] = os.Getenv("BUILDKITE_BUILD_URL") + tags[constants.CIJobURL] = fmt.Sprintf("%s#%s", os.Getenv("BUILDKITE_BUILD_URL"), os.Getenv("BUILDKITE_JOB_ID")) + tags[constants.CIProviderName] = "buildkite" + tags[constants.CIWorkspacePath] = os.Getenv("BUILDKITE_BUILD_CHECKOUT_PATH") + tags[constants.GitCommitMessage] = os.Getenv("BUILDKITE_MESSAGE") + tags[constants.GitCommitAuthorName] = os.Getenv("BUILDKITE_BUILD_AUTHOR") + tags[constants.GitCommitAuthorEmail] = os.Getenv("BUILDKITE_BUILD_AUTHOR_EMAIL") + tags[constants.CINodeName] = os.Getenv("BUILDKITE_AGENT_ID") + + jsonString, err := getEnvVarsJSON("BUILDKITE_BUILD_ID", "BUILDKITE_JOB_ID") + if err == nil { + tags[constants.CIEnvVars] = string(jsonString) + } + + var extraTags []string + envVars := os.Environ() + for _, envVar := range envVars { + if strings.HasPrefix(envVar, "BUILDKITE_AGENT_META_DATA_") { + envVarAsTag := envVar + envVarAsTag = strings.TrimPrefix(envVarAsTag, "BUILDKITE_AGENT_META_DATA_") + envVarAsTag = strings.ToLower(envVarAsTag) + envVarAsTag = strings.Replace(envVarAsTag, "=", ":", 1) + extraTags = append(extraTags, envVarAsTag) + } + } + + if len(extraTags) != 0 { + // HACK: Sorting isn't actually needed, but it simplifies testing if the order is consistent + sort.Sort(sort.Reverse(sort.StringSlice(extraTags))) + jsonString, err = json.Marshal(extraTags) + if err == nil { + tags[constants.CINodeLabels] = string(jsonString) + } + } + + return tags +} + +// extractCircleCI extracts CI information specific to CircleCI. +func extractCircleCI() map[string]string { + tags := map[string]string{} + tags[constants.CIProviderName] = "circleci" + tags[constants.GitRepositoryURL] = os.Getenv("CIRCLE_REPOSITORY_URL") + tags[constants.GitCommitSHA] = os.Getenv("CIRCLE_SHA1") + tags[constants.GitTag] = os.Getenv("CIRCLE_TAG") + tags[constants.GitBranch] = os.Getenv("CIRCLE_BRANCH") + tags[constants.CIWorkspacePath] = os.Getenv("CIRCLE_WORKING_DIRECTORY") + tags[constants.CIPipelineID] = os.Getenv("CIRCLE_WORKFLOW_ID") + tags[constants.CIPipelineName] = os.Getenv("CIRCLE_PROJECT_REPONAME") + tags[constants.CIPipelineNumber] = os.Getenv("CIRCLE_BUILD_NUM") + tags[constants.CIPipelineURL] = fmt.Sprintf("https://app.circleci.com/pipelines/workflows/%s", os.Getenv("CIRCLE_WORKFLOW_ID")) + tags[constants.CIJobName] = os.Getenv("CIRCLE_JOB") + tags[constants.CIJobURL] = os.Getenv("CIRCLE_BUILD_URL") + + jsonString, err := getEnvVarsJSON("CIRCLE_BUILD_NUM", "CIRCLE_WORKFLOW_ID") + if err == nil { + tags[constants.CIEnvVars] = string(jsonString) + } + + return tags +} + +// extractGithubActions extracts CI information specific to GitHub Actions. +func extractGithubActions() map[string]string { + tags := map[string]string{} + branchOrTag := firstEnv("GITHUB_HEAD_REF", "GITHUB_REF") + tag := "" + branch := "" + if strings.Contains(branchOrTag, "tags/") { + tag = branchOrTag + } else { + branch = branchOrTag + } + + serverURL := os.Getenv("GITHUB_SERVER_URL") + if serverURL == "" { + serverURL = "https://github.com" + } + serverURL = strings.TrimSuffix(serverURL, "/") + + rawRepository := fmt.Sprintf("%s/%s", serverURL, os.Getenv("GITHUB_REPOSITORY")) + pipelineID := os.Getenv("GITHUB_RUN_ID") + commitSha := os.Getenv("GITHUB_SHA") + + tags[constants.CIProviderName] = "github" + tags[constants.GitRepositoryURL] = rawRepository + ".git" + tags[constants.GitCommitSHA] = commitSha + tags[constants.GitBranch] = branch + tags[constants.GitTag] = tag + tags[constants.CIWorkspacePath] = os.Getenv("GITHUB_WORKSPACE") + tags[constants.CIPipelineID] = pipelineID + tags[constants.CIPipelineNumber] = os.Getenv("GITHUB_RUN_NUMBER") + tags[constants.CIPipelineName] = os.Getenv("GITHUB_WORKFLOW") + tags[constants.CIJobURL] = fmt.Sprintf("%s/commit/%s/checks", rawRepository, commitSha) + tags[constants.CIJobName] = os.Getenv("GITHUB_JOB") + + attempts := os.Getenv("GITHUB_RUN_ATTEMPT") + if attempts == "" { + tags[constants.CIPipelineURL] = fmt.Sprintf("%s/actions/runs/%s", rawRepository, pipelineID) + } else { + tags[constants.CIPipelineURL] = fmt.Sprintf("%s/actions/runs/%s/attempts/%s", rawRepository, pipelineID, attempts) + } + + jsonString, err := getEnvVarsJSON("GITHUB_SERVER_URL", "GITHUB_REPOSITORY", "GITHUB_RUN_ID", "GITHUB_RUN_ATTEMPT") + if err == nil { + tags[constants.CIEnvVars] = string(jsonString) + } + + return tags +} + +// extractGitlab extracts CI information specific to GitLab. +func extractGitlab() map[string]string { + tags := map[string]string{} + url := os.Getenv("CI_PIPELINE_URL") + + tags[constants.CIProviderName] = "gitlab" + tags[constants.GitRepositoryURL] = os.Getenv("CI_REPOSITORY_URL") + tags[constants.GitCommitSHA] = os.Getenv("CI_COMMIT_SHA") + tags[constants.GitBranch] = firstEnv("CI_COMMIT_BRANCH", "CI_COMMIT_REF_NAME") + tags[constants.GitTag] = os.Getenv("CI_COMMIT_TAG") + tags[constants.CIWorkspacePath] = os.Getenv("CI_PROJECT_DIR") + tags[constants.CIPipelineID] = os.Getenv("CI_PIPELINE_ID") + tags[constants.CIPipelineName] = os.Getenv("CI_PROJECT_PATH") + tags[constants.CIPipelineNumber] = os.Getenv("CI_PIPELINE_IID") + tags[constants.CIPipelineURL] = url + tags[constants.CIJobURL] = os.Getenv("CI_JOB_URL") + tags[constants.CIJobName] = os.Getenv("CI_JOB_NAME") + tags[constants.CIStageName] = os.Getenv("CI_JOB_STAGE") + tags[constants.GitCommitMessage] = os.Getenv("CI_COMMIT_MESSAGE") + tags[constants.CINodeName] = os.Getenv("CI_RUNNER_ID") + tags[constants.CINodeLabels] = os.Getenv("CI_RUNNER_TAGS") + + author := os.Getenv("CI_COMMIT_AUTHOR") + authorArray := strings.FieldsFunc(author, func(s rune) bool { + return s == '<' || s == '>' + }) + tags[constants.GitCommitAuthorName] = strings.TrimSpace(authorArray[0]) + tags[constants.GitCommitAuthorEmail] = strings.TrimSpace(authorArray[1]) + tags[constants.GitCommitAuthorDate] = os.Getenv("CI_COMMIT_TIMESTAMP") + + jsonString, err := getEnvVarsJSON("CI_PROJECT_URL", "CI_PIPELINE_ID", "CI_JOB_ID") + if err == nil { + tags[constants.CIEnvVars] = string(jsonString) + } + + return tags +} + +// extractJenkins extracts CI information specific to Jenkins. +func extractJenkins() map[string]string { + tags := map[string]string{} + tags[constants.CIProviderName] = "jenkins" + tags[constants.GitRepositoryURL] = firstEnv("GIT_URL", "GIT_URL_1") + tags[constants.GitCommitSHA] = os.Getenv("GIT_COMMIT") + + branchOrTag := os.Getenv("GIT_BRANCH") + empty := []byte("") + name, hasName := os.LookupEnv("JOB_NAME") + + if strings.Contains(branchOrTag, "tags/") { + tags[constants.GitTag] = branchOrTag + } else { + tags[constants.GitBranch] = branchOrTag + // remove branch for job name + removeBranch := regexp.MustCompile(fmt.Sprintf("/%s", normalizeRef(branchOrTag))) + name = string(removeBranch.ReplaceAll([]byte(name), empty)) + } + + if hasName { + removeVars := regexp.MustCompile("/[^/]+=[^/]*") + name = string(removeVars.ReplaceAll([]byte(name), empty)) + } + + tags[constants.CIWorkspacePath] = os.Getenv("WORKSPACE") + tags[constants.CIPipelineID] = os.Getenv("BUILD_TAG") + tags[constants.CIPipelineNumber] = os.Getenv("BUILD_NUMBER") + tags[constants.CIPipelineName] = name + tags[constants.CIPipelineURL] = os.Getenv("BUILD_URL") + tags[constants.CINodeName] = os.Getenv("NODE_NAME") + + jsonString, err := getEnvVarsJSON("DD_CUSTOM_TRACE_ID") + if err == nil { + tags[constants.CIEnvVars] = string(jsonString) + } + + nodeLabels := os.Getenv("NODE_LABELS") + if len(nodeLabels) > 0 { + labelsArray := strings.Split(nodeLabels, " ") + jsonString, err := json.Marshal(labelsArray) + if err == nil { + tags[constants.CINodeLabels] = string(jsonString) + } + } + + return tags +} + +// extractTeamcity extracts CI information specific to TeamCity. +func extractTeamcity() map[string]string { + tags := map[string]string{} + tags[constants.CIProviderName] = "teamcity" + tags[constants.CIJobURL] = os.Getenv("BUILD_URL") + tags[constants.CIJobName] = os.Getenv("TEAMCITY_BUILDCONF_NAME") + return tags +} + +// extractCodefresh extracts CI information specific to Codefresh. +func extractCodefresh() map[string]string { + tags := map[string]string{} + tags[constants.CIProviderName] = "codefresh" + tags[constants.CIPipelineID] = os.Getenv("CF_BUILD_ID") + tags[constants.CIPipelineName] = os.Getenv("CF_PIPELINE_NAME") + tags[constants.CIPipelineURL] = os.Getenv("CF_BUILD_URL") + tags[constants.CIJobName] = os.Getenv("CF_STEP_NAME") + + jsonString, err := getEnvVarsJSON("CF_BUILD_ID") + if err == nil { + tags[constants.CIEnvVars] = string(jsonString) + } + + cfBranch := os.Getenv("CF_BRANCH") + isTag := strings.Contains(cfBranch, "tags/") + var refKey string + if isTag { + refKey = constants.GitTag + } else { + refKey = constants.GitBranch + } + tags[refKey] = normalizeRef(cfBranch) + + return tags +} + +// extractTravis extracts CI information specific to Travis CI. +func extractTravis() map[string]string { + tags := map[string]string{} + prSlug := os.Getenv("TRAVIS_PULL_REQUEST_SLUG") + repoSlug := prSlug + if strings.TrimSpace(repoSlug) == "" { + repoSlug = os.Getenv("TRAVIS_REPO_SLUG") + } + tags[constants.CIProviderName] = "travisci" + tags[constants.GitRepositoryURL] = fmt.Sprintf("https://github.com/%s.git", repoSlug) + tags[constants.GitCommitSHA] = os.Getenv("TRAVIS_COMMIT") + tags[constants.GitTag] = os.Getenv("TRAVIS_TAG") + tags[constants.GitBranch] = firstEnv("TRAVIS_PULL_REQUEST_BRANCH", "TRAVIS_BRANCH") + tags[constants.CIWorkspacePath] = os.Getenv("TRAVIS_BUILD_DIR") + tags[constants.CIPipelineID] = os.Getenv("TRAVIS_BUILD_ID") + tags[constants.CIPipelineNumber] = os.Getenv("TRAVIS_BUILD_NUMBER") + tags[constants.CIPipelineName] = repoSlug + tags[constants.CIPipelineURL] = os.Getenv("TRAVIS_BUILD_WEB_URL") + tags[constants.CIJobURL] = os.Getenv("TRAVIS_JOB_WEB_URL") + tags[constants.GitCommitMessage] = os.Getenv("TRAVIS_COMMIT_MESSAGE") + return tags +} + +// extractAwsCodePipeline extracts CI information specific to AWS CodePipeline. +func extractAwsCodePipeline() map[string]string { + tags := map[string]string{} + + if !strings.HasPrefix(os.Getenv("CODEBUILD_INITIATOR"), "codepipeline") { + // CODEBUILD_INITIATOR is defined but this is not a codepipeline build + return tags + } + + tags[constants.CIProviderName] = "awscodepipeline" + tags[constants.CIPipelineID] = os.Getenv("DD_PIPELINE_EXECUTION_ID") + + jsonString, err := getEnvVarsJSON("CODEBUILD_BUILD_ARN", "DD_ACTION_EXECUTION_ID", "DD_PIPELINE_EXECUTION_ID") + if err == nil { + tags[constants.CIEnvVars] = string(jsonString) + } + + return tags +} diff --git a/internal/civisibility/utils/ci_providers_test.go b/internal/civisibility/utils/ci_providers_test.go new file mode 100644 index 0000000000..b85acc1efc --- /dev/null +++ b/internal/civisibility/utils/ci_providers_test.go @@ -0,0 +1,116 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" +) + +func setEnvs(t *testing.T, env map[string]string) { + for key, value := range env { + t.Setenv(key, value) + } +} + +func sortJSONKeys(jsonStr string) string { + tmp := map[string]string{} + _ = json.Unmarshal([]byte(jsonStr), &tmp) + jsonBytes, _ := json.Marshal(tmp) + return string(jsonBytes) +} + +// TestTags asserts that all tags are extracted from environment variables. +func TestTags(t *testing.T) { + // Reset provider env key when running in CI + resetProviders := map[string]string{} + for key := range providers { + if value, ok := os.LookupEnv(key); ok { + resetProviders[key] = value + _ = os.Unsetenv(key) + } + } + defer func() { + for key, value := range resetProviders { + _ = os.Setenv(key, value) + } + }() + + paths, err := filepath.Glob("testdata/fixtures/providers/*.json") + if err != nil { + t.Fatal(err) + } + for _, path := range paths { + providerName := strings.TrimSuffix(filepath.Base(path), ".json") + + t.Run(providerName, func(t *testing.T) { + fp, err := os.Open(path) + if err != nil { + t.Fatal(err) + } + + data, err := io.ReadAll(fp) + if err != nil { + t.Fatal(err) + } + + var examples [][]map[string]string + if err := json.Unmarshal(data, &examples); err != nil { + t.Fatal(err) + } + + for i, line := range examples { + name := fmt.Sprintf("%d", i) + env := line[0] + tags := line[1] + + // Because we have a fallback algorithm for some variables + // we need to initialize some of them to not use the one set by the github action running this test. + if providerName == "github" { + // We initialize GITHUB_RUN_ATTEMPT if it doesn't exist to avoid using the one set in the GitHub action. + if _, ok := env["GITHUB_RUN_ATTEMPT"]; !ok { + env["GITHUB_RUN_ATTEMPT"] = "" + } + // We initialize GITHUB_HEAD_REF if it doesn't exist to avoid using the one set in the GitHub action. + if _, ok := env["GITHUB_HEAD_REF"]; !ok { + env["GITHUB_HEAD_REF"] = "" + } + // We initialize GITHUB_REF if it doesn't exist to avoid using the one set in the GitHub action. + if _, ok := env["GITHUB_REF"]; !ok { + env["GITHUB_REF"] = "" + } + } + + t.Run(name, func(t *testing.T) { + setEnvs(t, env) + providerTags := getProviderTags() + + for expectedKey, expectedValue := range tags { + if actualValue, ok := providerTags[expectedKey]; ok { + if expectedKey == "_dd.ci.env_vars" { + expectedValue = sortJSONKeys(expectedValue) + } + if expectedValue != actualValue { + if expectedValue == strings.ReplaceAll(actualValue, "\\", "/") { + continue + } + + t.Fatalf("Key: %s, the actual value (%s) is different to the expected value (%s)", expectedKey, actualValue, expectedValue) + } + } else { + t.Fatalf("Key: %s, doesn't exist.", expectedKey) + } + } + }) + } + }) + } +} diff --git a/internal/civisibility/utils/codeowners.go b/internal/civisibility/utils/codeowners.go new file mode 100644 index 0000000000..fa930d5af2 --- /dev/null +++ b/internal/civisibility/utils/codeowners.go @@ -0,0 +1,304 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" + + logger "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) + +// This is a port of https://github.com/DataDog/dd-trace-dotnet/blob/v2.53.0/tracer/src/Datadog.Trace/Ci/CodeOwners.cs + +type ( + // CodeOwners represents a structured data type that holds sections of code owners. + // Each section maps to a slice of entries, where each entry includes a pattern and a list of owners. + CodeOwners struct { + Sections []*Section + } + + // Section represents a block of structured data of multiple entries in a single section + Section struct { + Name string + Entries []Entry + } + + // Entry represents a single entry in a CODEOWNERS file. + // It includes the pattern for matching files, the list of owners, and the section to which it belongs. + Entry struct { + Pattern string + Owners []string + Section string + } +) + +var ( + // codeowners holds the parsed CODEOWNERS file data. + codeowners *CodeOwners + codeownersMutex sync.Mutex +) + +// GetCodeOwners retrieves and caches the CODEOWNERS data. +// It looks for the CODEOWNERS file in various standard locations within the CI workspace. +// This function is thread-safe due to the use of a mutex. +// +// Returns: +// +// A pointer to a CodeOwners struct containing the parsed CODEOWNERS data, or nil if not found. +func GetCodeOwners() *CodeOwners { + codeownersMutex.Lock() + defer codeownersMutex.Unlock() + + if codeowners != nil { + return codeowners + } + + tags := GetCITags() + if v, ok := tags[constants.CIWorkspacePath]; ok { + paths := []string{ + filepath.Join(v, "CODEOWNERS"), + filepath.Join(v, ".github", "CODEOWNERS"), + filepath.Join(v, ".gitlab", "CODEOWNERS"), + filepath.Join(v, ".docs", "CODEOWNERS"), + } + for _, path := range paths { + if _, err := os.Stat(path); err == nil { + codeowners, err = NewCodeOwners(path) + if err == nil { + return codeowners + } + logger.Debug("Error parsing codeowners: %s", err) + } + } + } + + return nil +} + +// NewCodeOwners creates a new instance of CodeOwners by parsing a CODEOWNERS file located at the given filePath. +// It returns an error if the file cannot be read or parsed properly. +func NewCodeOwners(filePath string) (*CodeOwners, error) { + if filePath == "" { + return nil, fmt.Errorf("filePath cannot be empty") + } + + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer func() { + err = file.Close() + if err != nil && !errors.Is(os.ErrClosed, err) { + logger.Warn("Error closing codeowners file: %s", err.Error()) + } + }() + + var entriesList []Entry + var sectionsList []string + var currentSectionName string + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if len(line) == 0 || line[0] == '#' { + continue + } + + // Identify section headers, which are lines enclosed in square brackets + if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { + currentSectionName = line[1 : len(line)-1] + foundSectionName := findSectionIgnoreCase(sectionsList, currentSectionName) + if foundSectionName == "" { + sectionsList = append(sectionsList, currentSectionName) + } else { + currentSectionName = foundSectionName + } + continue + } + + finalLine := line + var ownersList []string + terms := strings.Fields(line) + for _, term := range terms { + if len(term) == 0 { + continue + } + + // Identify owners by their prefixes (either @ for usernames or containing @ for emails) + if term[0] == '@' || strings.Contains(term, "@") { + ownersList = append(ownersList, term) + pos := strings.Index(finalLine, term) + if pos > 0 { + finalLine = finalLine[:pos] + finalLine[pos+len(term):] + } + } + } + + finalLine = strings.TrimSpace(finalLine) + if len(finalLine) == 0 { + continue + } + + entriesList = append(entriesList, Entry{Pattern: finalLine, Owners: ownersList, Section: currentSectionName}) + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + // Reverse the entries list to maintain the order of appearance in the file + for i, j := 0, len(entriesList)-1; i < j; i, j = i+1, j-1 { + entriesList[i], entriesList[j] = entriesList[j], entriesList[i] + } + + codeOwners := &CodeOwners{} + for _, entry := range entriesList { + var section *Section + for _, val := range codeOwners.Sections { + if val.Name == entry.Section { + section = val + break + } + } + + if section == nil { + section = &Section{Name: entry.Section, Entries: []Entry{}} + codeOwners.Sections = append(codeOwners.Sections, section) + } + + section.Entries = append(section.Entries, entry) + } + + return codeOwners, nil +} + +// findSectionIgnoreCase searches for a section name in a case-insensitive manner. +// It returns the section name if found, otherwise returns an empty string. +func findSectionIgnoreCase(sections []string, section string) string { + sectionLower := strings.ToLower(section) + for _, s := range sections { + if strings.ToLower(s) == sectionLower { + return s + } + } + return "" +} + +// GetSection gets the first Section entry in the CodeOwners that matches the section name. +// It returns a pointer to the matched entry, or nil if no match is found +func (co *CodeOwners) GetSection(section string) *Section { + for _, value := range co.Sections { + if value.Name == section { + return value + } + } + + return nil +} + +// Match finds the first entry in the CodeOwners that matches the given value. +// It returns a pointer to the matched entry, or nil if no match is found. +func (co *CodeOwners) Match(value string) (*Entry, bool) { + var matchedEntries []Entry + + for _, section := range co.Sections { + for _, entry := range section.Entries { + pattern := entry.Pattern + finalPattern := pattern + + var includeAnythingBefore, includeAnythingAfter bool + + if strings.HasPrefix(pattern, "/") { + includeAnythingBefore = false + } else { + if strings.HasPrefix(finalPattern, "*") { + finalPattern = finalPattern[1:] + } + includeAnythingBefore = true + } + + if strings.HasSuffix(pattern, "/") { + includeAnythingAfter = true + } else if strings.HasSuffix(pattern, "/*") { + includeAnythingAfter = true + finalPattern = finalPattern[:len(finalPattern)-1] + } else { + includeAnythingAfter = false + } + + if includeAnythingAfter { + found := includeAnythingBefore && strings.Contains(value, finalPattern) || strings.HasPrefix(value, finalPattern) + if !found { + continue + } + + if !strings.HasSuffix(pattern, "/*") { + matchedEntries = append(matchedEntries, entry) + break + } + + patternEnd := strings.Index(value, finalPattern) + if patternEnd != -1 { + patternEnd += len(finalPattern) + remainingString := value[patternEnd:] + if strings.Index(remainingString, "/") == -1 { + matchedEntries = append(matchedEntries, entry) + break + } + } + } else { + if includeAnythingBefore { + if strings.HasSuffix(value, finalPattern) { + matchedEntries = append(matchedEntries, entry) + break + } + } else if value == finalPattern { + matchedEntries = append(matchedEntries, entry) + break + } + } + } + } + + switch len(matchedEntries) { + case 0: + return nil, false + case 1: + return &matchedEntries[0], true + default: + patterns := make([]string, 0) + owners := make([]string, 0) + sections := make([]string, 0) + for _, entry := range matchedEntries { + patterns = append(patterns, entry.Pattern) + owners = append(owners, entry.Owners...) + sections = append(sections, entry.Section) + } + return &Entry{ + Pattern: strings.Join(patterns, " | "), + Owners: owners, + Section: strings.Join(sections, " | "), + }, true + } +} + +// GetOwnersString returns a formatted string of the owners list in an Entry. +// It returns an empty string if there are no owners. +func (e Entry) GetOwnersString() string { + if e.Owners == nil || len(e.Owners) == 0 { + return "" + } + + return "[\"" + strings.Join(e.Owners, "\",\"") + "\"]" +} diff --git a/internal/civisibility/utils/codeowners_test.go b/internal/civisibility/utils/codeowners_test.go new file mode 100644 index 0000000000..95852bec4a --- /dev/null +++ b/internal/civisibility/utils/codeowners_test.go @@ -0,0 +1,159 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewCodeOwners(t *testing.T) { + // Create a temporary file for testing + fileContent := `[Section 1] +/path/to/file @owner1 @owner2 +/path/to/* @owner3 + +[Section 2] +/another/path @owner4 +` + + tmpFile, err := os.CreateTemp("", "CODEOWNERS") + assert.NoError(t, err) + defer os.Remove(tmpFile.Name()) + + _, err = tmpFile.WriteString(fileContent) + assert.NoError(t, err) + + err = tmpFile.Close() + assert.NoError(t, err) + + // Test NewCodeOwners + codeOwners, err := NewCodeOwners(tmpFile.Name()) + assert.NoError(t, err) + assert.NotNil(t, codeOwners) + assert.Equal(t, 2, len(codeOwners.Sections)) + assert.Equal(t, 2, len(codeOwners.GetSection("Section 1").Entries)) + assert.Equal(t, 1, len(codeOwners.GetSection("Section 2").Entries)) + + // Test empty file path + _, err = NewCodeOwners("") + assert.Error(t, err) +} + +func TestFindSectionIgnoreCase(t *testing.T) { + sections := []string{"Section1", "section2", "SECTION3"} + assert.Equal(t, "Section1", findSectionIgnoreCase(sections, "section1")) + assert.Equal(t, "section2", findSectionIgnoreCase(sections, "SECTION2")) + assert.Equal(t, "SECTION3", findSectionIgnoreCase(sections, "Section3")) + assert.Equal(t, "", findSectionIgnoreCase(sections, "Section4")) +} + +func TestMatch(t *testing.T) { + entries := []Entry{ + {Pattern: "/path/to/file", Owners: []string{"@owner1", "@owner2"}, Section: "Section 1"}, + {Pattern: "/path/to/*", Owners: []string{"@owner3"}, Section: "Section 1"}, + {Pattern: "/another/path", Owners: []string{"@owner4"}, Section: "Section 2"}, + } + sections := []*Section{ + {Name: "Section 1", Entries: []Entry{entries[0], entries[1]}}, + {Name: "Section 2", Entries: []Entry{entries[2]}}, + } + + codeOwners := &CodeOwners{Sections: sections} + + // Test exact match + entry, ok := codeOwners.Match("/path/to/file") + assert.True(t, ok) + assert.Equal(t, entries[0], *entry) + + // Test wildcard match + entry, ok = codeOwners.Match("/path/to/anything") + assert.True(t, ok) + assert.Equal(t, entries[1], *entry) + + // Test no match + entry, ok = codeOwners.Match("/no/match") + assert.False(t, ok) +} + +func TestGetOwnersString(t *testing.T) { + entry := Entry{Owners: []string{"@owner1", "@owner2"}} + assert.Equal(t, "[\"@owner1\",\"@owner2\"]", entry.GetOwnersString()) + + entry = Entry{} + assert.Equal(t, "", entry.GetOwnersString()) +} + +func TestGithubCodeOwners(t *testing.T) { + cOwners, err := NewCodeOwners("testdata/fixtures/codeowners/CODEOWNERS_GITHUB") + if err != nil { + t.Fatal(err) + } + if cOwners == nil { + t.Fatal("nil codeowners") + } + + data := []struct { + value string + expected string + }{ + {value: "unexistent/path/test.cs", expected: "[\"@global-owner1\",\"@global-owner2\"]"}, + {value: "apps/test.cs", expected: "[\"@octocat\"]"}, + {value: "/example/apps/test.cs", expected: "[\"@octocat\"]"}, + {value: "/docs/test.cs", expected: "[\"@doctocat\"]"}, + {value: "/examples/docs/test.cs", expected: "[\"docs@example.com\"]"}, + {value: "/src/vendor/match.go", expected: "[\"docs@example.com\"]"}, + {value: "/examples/docs/inside/test.cs", expected: "[\"@global-owner1\",\"@global-owner2\"]"}, + {value: "/component/path/test.js", expected: "[\"@js-owner\"]"}, + {value: "/mytextbox.txt", expected: "[\"@octo-org/octocats\"]"}, + {value: "/scripts/artifacts/value.js", expected: "[\"@doctocat\",\"@octocat\"]"}, + {value: "/apps/octo/test.cs", expected: "[\"@octocat\"]"}, + {value: "/apps/github", expected: ""}, + } + + for _, item := range data { + t.Run(strings.ReplaceAll(item.value, "/", "_"), func(t *testing.T) { + match, ok := cOwners.Match(item.value) + assert.True(t, ok) + assert.EqualValues(t, item.expected, match.GetOwnersString()) + }) + } +} + +func TestGitlabCodeOwners(t *testing.T) { + cOwners, err := NewCodeOwners("testdata/fixtures/codeowners/CODEOWNERS_GITLAB") + if err != nil { + t.Fatal(err) + } + if cOwners == nil { + t.Fatal("nil codeowners") + } + + data := []struct { + value string + expected string + }{ + {value: "apps/README.md", expected: "[\"@docs\",\"@database\",\"@multiple\",\"@code\",\"@owners\"]"}, + {value: "model/db", expected: "[\"@database\",\"@multiple\",\"@code\",\"@owners\"]"}, + {value: "/config/data.conf", expected: "[\"@config-owner\"]"}, + {value: "/docs/root.md", expected: "[\"@root-docs\"]"}, + {value: "/docs/sub/root.md", expected: "[\"@all-docs\"]"}, + {value: "/src/README", expected: "[\"@group\",\"@group/with-nested/subgroup\"]"}, + {value: "/src/lib/internal.h", expected: "[\"@lib-owner\"]"}, + {value: "src/ee/docs", expected: "[\"@docs\",\"@multiple\",\"@code\",\"@owners\"]"}, + } + + for _, item := range data { + t.Run(strings.ReplaceAll(item.value, "/", "_"), func(t *testing.T) { + match, ok := cOwners.Match(item.value) + assert.True(t, ok) + assert.EqualValues(t, item.expected, match.GetOwnersString()) + }) + } +} diff --git a/internal/civisibility/utils/environmentTags.go b/internal/civisibility/utils/environmentTags.go new file mode 100644 index 0000000000..0b64b8be86 --- /dev/null +++ b/internal/civisibility/utils/environmentTags.go @@ -0,0 +1,119 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "path/filepath" + "runtime" + "sync" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" + "gopkg.in/DataDog/dd-trace-go.v1/internal/osinfo" +) + +var ( + // ciTags holds the CI/CD environment variable information. + ciTags map[string]string + ciTagsMutex sync.Mutex +) + +// GetCITags retrieves and caches the CI/CD tags from environment variables. +// It initializes the ciTags map if it is not already initialized. +// This function is thread-safe due to the use of a mutex. +// +// Returns: +// +// A map[string]string containing the CI/CD tags. +func GetCITags() map[string]string { + ciTagsMutex.Lock() + defer ciTagsMutex.Unlock() + + if ciTags == nil { + ciTags = createCITagsMap() + } + + return ciTags +} + +// GetRelativePathFromCITagsSourceRoot calculates the relative path from the CI workspace root to the specified path. +// If the CI workspace root is not available in the tags, it returns the original path. +// +// Parameters: +// +// path - The absolute or relative file path for which the relative path should be calculated. +// +// Returns: +// +// The relative path from the CI workspace root to the specified path, or the original path if an error occurs. +func GetRelativePathFromCITagsSourceRoot(path string) string { + tags := GetCITags() + if v, ok := tags[constants.CIWorkspacePath]; ok { + relPath, err := filepath.Rel(v, path) + if err == nil { + return filepath.ToSlash(relPath) + } + } + + return path +} + +// createCITagsMap creates a map of CI/CD tags by extracting information from environment variables and the local Git repository. +// It also adds OS and runtime information to the tags. +// +// Returns: +// +// A map[string]string containing the extracted CI/CD tags. +func createCITagsMap() map[string]string { + localTags := getProviderTags() + localTags[constants.OSPlatform] = runtime.GOOS + localTags[constants.OSVersion] = osinfo.OSVersion() + localTags[constants.OSArchitecture] = runtime.GOARCH + localTags[constants.RuntimeName] = runtime.Compiler + localTags[constants.RuntimeVersion] = runtime.Version() + + gitData, _ := getLocalGitData() + + // Populate Git metadata from the local Git repository if not already present in localTags + if _, ok := localTags[constants.CIWorkspacePath]; !ok { + localTags[constants.CIWorkspacePath] = gitData.SourceRoot + } + if _, ok := localTags[constants.GitRepositoryURL]; !ok { + localTags[constants.GitRepositoryURL] = gitData.RepositoryURL + } + if _, ok := localTags[constants.GitCommitSHA]; !ok { + localTags[constants.GitCommitSHA] = gitData.CommitSha + } + if _, ok := localTags[constants.GitBranch]; !ok { + localTags[constants.GitBranch] = gitData.Branch + } + + // If the commit SHA matches, populate additional Git metadata + if localTags[constants.GitCommitSHA] == gitData.CommitSha { + if _, ok := localTags[constants.GitCommitAuthorDate]; !ok { + localTags[constants.GitCommitAuthorDate] = gitData.AuthorDate.String() + } + if _, ok := localTags[constants.GitCommitAuthorName]; !ok { + localTags[constants.GitCommitAuthorName] = gitData.AuthorName + } + if _, ok := localTags[constants.GitCommitAuthorEmail]; !ok { + localTags[constants.GitCommitAuthorEmail] = gitData.AuthorEmail + } + if _, ok := localTags[constants.GitCommitCommitterDate]; !ok { + localTags[constants.GitCommitCommitterDate] = gitData.CommitterDate.String() + } + if _, ok := localTags[constants.GitCommitCommitterName]; !ok { + localTags[constants.GitCommitCommitterName] = gitData.CommitterName + } + if _, ok := localTags[constants.GitCommitCommitterEmail]; !ok { + localTags[constants.GitCommitCommitterEmail] = gitData.CommitterEmail + } + if _, ok := localTags[constants.GitCommitMessage]; !ok { + localTags[constants.GitCommitMessage] = gitData.CommitMessage + } + } + + return localTags +} diff --git a/internal/civisibility/utils/environmentTags_test.go b/internal/civisibility/utils/environmentTags_test.go new file mode 100644 index 0000000000..694a2b1217 --- /dev/null +++ b/internal/civisibility/utils/environmentTags_test.go @@ -0,0 +1,40 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "testing" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants" + + "github.com/stretchr/testify/assert" +) + +func TestGetCITagsCache(t *testing.T) { + ciTags = map[string]string{"key": "value"} + + // First call to initialize ciTags + tags := GetCITags() + assert.Equal(t, "value", tags["key"]) + + tags["key"] = "newvalue" + tags = GetCITags() + assert.Equal(t, "newvalue", tags["key"]) +} + +func TestGetRelativePathFromCITagsSourceRoot(t *testing.T) { + ciTags = map[string]string{constants.CIWorkspacePath: "/ci/workspace"} + absPath := "/ci/workspace/subdir/file.txt" + expectedRelPath := "subdir/file.txt" + + relPath := GetRelativePathFromCITagsSourceRoot(absPath) + assert.Equal(t, expectedRelPath, relPath) + + // Test case when CIWorkspacePath is not set in ciTags + ciTags = map[string]string{} + relPath = GetRelativePathFromCITagsSourceRoot(absPath) + assert.Equal(t, absPath, relPath) +} diff --git a/internal/civisibility/utils/git.go b/internal/civisibility/utils/git.go new file mode 100644 index 0000000000..fb62a37567 --- /dev/null +++ b/internal/civisibility/utils/git.go @@ -0,0 +1,104 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "errors" + "os/exec" + "regexp" + "strconv" + "strings" + "time" +) + +// localGitData holds various pieces of information about the local Git repository, +// including the source root, repository URL, branch, commit SHA, author and committer details, and commit message. +type localGitData struct { + SourceRoot string + RepositoryURL string + Branch string + CommitSha string + AuthorDate time.Time + AuthorName string + AuthorEmail string + CommitterDate time.Time + CommitterName string + CommitterEmail string + CommitMessage string +} + +// regexpSensitiveInfo is a regular expression used to match and filter out sensitive information from URLs. +var regexpSensitiveInfo = regexp.MustCompile("(https?://|ssh?://)[^/]*@") + +// getLocalGitData retrieves information about the local Git repository from the current HEAD. +// It gathers details such as the repository URL, current branch, latest commit SHA, author and committer details, and commit message. +// +// Returns: +// +// A localGitData struct populated with the retrieved Git data. +// An error if any Git command fails or the retrieved data is incomplete. +func getLocalGitData() (localGitData, error) { + gitData := localGitData{} + + // Extract the absolute path to the Git directory + out, err := exec.Command("git", "rev-parse", "--absolute-git-dir").Output() + if err == nil { + gitData.SourceRoot = strings.ReplaceAll(strings.Trim(string(out), "\n"), ".git", "") + } + + // Extract the repository URL + out, err = exec.Command("git", "ls-remote", "--get-url").Output() + if err == nil { + gitData.RepositoryURL = filterSensitiveInfo(strings.Trim(string(out), "\n")) + } + + // Extract the current branch name + out, err = exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD").Output() + if err == nil { + gitData.Branch = strings.Trim(string(out), "\n") + } + + // Get commit details from the latest commit using git log (git log -1 --pretty='%H","%aI","%an","%ae","%cI","%cn","%ce","%B') + out, err = exec.Command("git", "log", "-1", "--pretty=%H\",\"%at\",\"%an\",\"%ae\",\"%ct\",\"%cn\",\"%ce\",\"%B").Output() + if err != nil { + return gitData, err + } + + // Split the output into individual components + outArray := strings.Split(string(out), "\",\"") + if len(outArray) < 8 { + return gitData, errors.New("git log failed") + } + + // Parse author and committer dates from Unix timestamp + authorUnixDate, _ := strconv.ParseInt(outArray[1], 10, 64) + committerUnixDate, _ := strconv.ParseInt(outArray[4], 10, 64) + + // Populate the localGitData struct with the parsed information + gitData.CommitSha = outArray[0] + gitData.AuthorDate = time.Unix(authorUnixDate, 0) + gitData.AuthorName = outArray[2] + gitData.AuthorEmail = outArray[3] + gitData.CommitterDate = time.Unix(committerUnixDate, 0) + gitData.CommitterName = outArray[5] + gitData.CommitterEmail = outArray[6] + gitData.CommitMessage = strings.Trim(outArray[7], "\n") + return gitData, nil +} + +// filterSensitiveInfo removes sensitive information from a given URL using a regular expression. +// It replaces the user credentials part of the URL (if present) with an empty string. +// +// Parameters: +// +// url - The URL string from which sensitive information should be filtered out. +// +// Returns: +// +// The sanitized URL string with sensitive information removed. +func filterSensitiveInfo(url string) string { + return string(regexpSensitiveInfo.ReplaceAll([]byte(url), []byte("$1"))[:]) +} diff --git a/internal/civisibility/utils/git_test.go b/internal/civisibility/utils/git_test.go new file mode 100644 index 0000000000..fd91ebee11 --- /dev/null +++ b/internal/civisibility/utils/git_test.go @@ -0,0 +1,62 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFilterSensitiveInfo(t *testing.T) { + tests := []struct { + input string + expected string + }{ + // Basic cases + {"https://user:pass@github.com/repo.git", "https://github.com/repo.git"}, + {"ssh://user@github.com/repo.git", "ssh://github.com/repo.git"}, + {"https://github.com/repo.git", "https://github.com/repo.git"}, + {"http://user:pass@github.com/repo.git", "http://github.com/repo.git"}, + + // Edge cases + {"", ""}, + {"https://@github.com/repo.git", "https://github.com/repo.git"}, + {"ftp://user@github.com/repo.git", "ftp://user@github.com/repo.git"}, // Unsupported protocol, should remain unchanged + {"user@github.com/repo.git", "user@github.com/repo.git"}, // No protocol, should remain unchanged + + // Complex cases + {"https://user:pass@github.com:8080/repo.git", "https://github.com:8080/repo.git"}, + {"ssh://user:auth@github.com/repo.git", "ssh://github.com/repo.git"}, + {"https://user:password@bitbucket.org/repo.git", "https://bitbucket.org/repo.git"}, + + // Cases with special characters + {"https://user:pa$$word@github.com/repo.git", "https://github.com/repo.git"}, + {"ssh://user!@github.com/repo.git", "ssh://github.com/repo.git"}, + {"https://user%40example.com@github.com/repo.git", "https://github.com/repo.git"}, // Encoded @ in username + } + + for _, test := range tests { + result := filterSensitiveInfo(test.input) + assert.Equal(t, test.expected, result, "Failed for input: %s", test.input) + } +} + +func TestGetLocalGitData(t *testing.T) { + data, err := getLocalGitData() + + assert.NoError(t, err) + assert.NotEmpty(t, data.SourceRoot) + assert.NotEmpty(t, data.RepositoryURL) + assert.NotEmpty(t, data.CommitSha) + assert.NotEmpty(t, data.AuthorName) + assert.NotEmpty(t, data.AuthorEmail) + assert.NotEmpty(t, data.AuthorDate) + assert.NotEmpty(t, data.CommitterName) + assert.NotEmpty(t, data.CommitterEmail) + assert.NotEmpty(t, data.CommitterDate) + assert.NotEmpty(t, data.CommitMessage) +} diff --git a/internal/civisibility/utils/home.go b/internal/civisibility/utils/home.go new file mode 100644 index 0000000000..8625010feb --- /dev/null +++ b/internal/civisibility/utils/home.go @@ -0,0 +1,120 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "bytes" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" +) + +// This code is based on: https://github.com/mitchellh/go-homedir/blob/v1.1.0/homedir.go (MIT License) + +// ExpandPath expands a file path that starts with '~' to the user's home directory. +// If the path does not start with '~', it is returned unchanged. +// +// Parameters: +// +// path - The file path to be expanded. +// +// Returns: +// +// The expanded file path, with '~' replaced by the user's home directory, if applicable. +func ExpandPath(path string) string { + if len(path) == 0 || path[0] != '~' { + return path + } + + // If the second character is not '/' or '\', return the path unchanged + if len(path) > 1 && path[1] != '/' && path[1] != '\\' { + return path + } + + homeFolder := getHomeDir() + if len(homeFolder) > 0 { + return filepath.Join(homeFolder, path[1:]) + } + + return path +} + +// getHomeDir returns the home directory of the current user. +// The method used to determine the home directory depends on the operating system. +// +// On Windows, it prefers the HOME environment variable, then USERPROFILE, and finally combines HOMEDRIVE and HOMEPATH. +// On Unix-like systems, it prefers the HOME environment variable, and falls back to various shell commands +// to determine the home directory if necessary. +// +// Returns: +// +// The home directory of the current user. +func getHomeDir() string { + if runtime.GOOS == "windows" { + if home := os.Getenv("HOME"); home != "" { + // First prefer the HOME environment variable + return home + } + if userProfile := os.Getenv("USERPROFILE"); userProfile != "" { + // Prefer the USERPROFILE environment variable + return userProfile + } + + homeDrive := os.Getenv("HOMEDRIVE") + homePath := os.Getenv("HOMEPATH") + return homeDrive + homePath + } + + homeEnv := "HOME" + if runtime.GOOS == "plan9" { + // On plan9, environment variables are lowercase. + homeEnv = "home" + } + + if home := os.Getenv(homeEnv); home != "" { + // Prefer the HOME environment variable + return home + } + + var stdout bytes.Buffer + if runtime.GOOS == "darwin" { + // On macOS, use dscl to read the NFSHomeDirectory + cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`) + cmd.Stdout = &stdout + if err := cmd.Run(); err == nil { + result := strings.TrimSpace(stdout.String()) + if result != "" { + return result + } + } + } else { + // On other Unix-like systems, use getent to read the passwd entry for the current user + cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid())) + cmd.Stdout = &stdout + if err := cmd.Run(); err == nil { + if passwd := strings.TrimSpace(stdout.String()); passwd != "" { + // The passwd entry is in the format: username:password:uid:gid:gecos:home:shell + passwdParts := strings.SplitN(passwd, ":", 7) + if len(passwdParts) > 5 { + return passwdParts[5] + } + } + } + } + + // If all else fails, use the shell to determine the home directory + stdout.Reset() + cmd := exec.Command("sh", "-c", "cd && pwd") + cmd.Stdout = &stdout + if err := cmd.Run(); err == nil { + return strings.TrimSpace(stdout.String()) + } + + return "" +} diff --git a/internal/civisibility/utils/home_test.go b/internal/civisibility/utils/home_test.go new file mode 100644 index 0000000000..a87d8de8d2 --- /dev/null +++ b/internal/civisibility/utils/home_test.go @@ -0,0 +1,97 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "os" + "os/user" + "path/filepath" + "testing" +) + +func patchEnv(key, value string) func() { + bck := os.Getenv(key) + deferFunc := func() { + _ = os.Setenv(key, bck) + } + + if value != "" { + _ = os.Setenv(key, value) + } else { + _ = os.Unsetenv(key) + } + + return deferFunc +} + +func TestDir(t *testing.T) { + u, err := user.Current() + if err != nil { + t.Fatalf("err: %s", err) + } + + dir := getHomeDir() + if u.HomeDir != dir { + t.Fatalf("%#v != %#v", u.HomeDir, dir) + } + + defer patchEnv("HOME", "")() + dir = getHomeDir() + if u.HomeDir != dir { + t.Fatalf("%#v != %#v", u.HomeDir, dir) + } +} + +func TestExpand(t *testing.T) { + u, err := user.Current() + if err != nil { + t.Fatalf("err: %s", err) + } + + cases := []struct { + Input string + Output string + }{ + { + "/foo", + "/foo", + }, + + { + "~/foo", + filepath.Join(u.HomeDir, "foo"), + }, + + { + "", + "", + }, + + { + "~", + u.HomeDir, + }, + + { + "~foo/foo", + "~foo/foo", + }, + } + + for _, tc := range cases { + actual := ExpandPath(tc.Input) + if actual != tc.Output { + t.Fatalf("Input: %#v\n\nOutput: %#v", tc.Input, actual) + } + } + + defer patchEnv("HOME", "/custom/path/")() + expected := filepath.Join("/", "custom", "path", "foo/bar") + actual := ExpandPath("~/foo/bar") + if actual != expected { + t.Errorf("Expected: %v; actual: %v", expected, actual) + } +} diff --git a/internal/civisibility/utils/names.go b/internal/civisibility/utils/names.go new file mode 100644 index 0000000000..1edcb2b972 --- /dev/null +++ b/internal/civisibility/utils/names.go @@ -0,0 +1,82 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "bytes" + "fmt" + "path/filepath" + "runtime" + "strings" +) + +// GetModuleAndSuiteName extracts the module name and suite name from a given program counter (pc). +// This function utilizes runtime.FuncForPC to retrieve the full function name associated with the +// program counter, then splits the string to separate the package name from the function name. +// +// Example 1: +// +// Input: github.com/DataDog/dd-sdk-go-testing.TestRun +// Output: +// module: github.com/DataDog/dd-sdk-go-testing +// suite: testing_test.go +// +// Example 2: +// +// Input: github.com/DataDog/dd-sdk-go-testing.TestRun.func1 +// Output: +// module: github.com/DataDog/dd-sdk-go-testing +// suite: testing_test.go +// +// Parameters: +// +// pc - The program counter for which the module and suite name should be retrieved. +// +// Returns: +// +// module - The module name extracted from the full function name. +// suite - The base name of the file where the function is located. +func GetModuleAndSuiteName(pc uintptr) (module string, suite string) { + funcValue := runtime.FuncForPC(pc) + funcFullName := funcValue.Name() + lastSlash := strings.LastIndexByte(funcFullName, '/') + if lastSlash < 0 { + lastSlash = 0 + } + firstDot := strings.IndexByte(funcFullName[lastSlash:], '.') + lastSlash + file, _ := funcValue.FileLine(funcValue.Entry()) + return funcFullName[:firstDot], filepath.Base(file) +} + +// GetStacktrace retrieves the current stack trace, skipping a specified number of frames. +// +// This function captures the stack trace of the current goroutine, formats it, and returns it as a string. +// It uses runtime.Callers to capture the program counters of the stack frames and runtime.CallersFrames +// to convert these program counters into readable frames. The stack trace is formatted to include the function +// name, file name, and line number of each frame. +// +// Parameters: +// +// skip - The number of stack frames to skip before capturing the stack trace. +// +// Returns: +// +// A string representation of the current stack trace, with each frame on a new line. +func GetStacktrace(skip int) string { + pcs := make([]uintptr, 256) + total := runtime.Callers(skip+2, pcs) + frames := runtime.CallersFrames(pcs[:total]) + buffer := new(bytes.Buffer) + for { + if frame, ok := frames.Next(); ok { + _, _ = fmt.Fprintf(buffer, "%s\n\t%s:%d\n", frame.Function, frame.File, frame.Line) + } else { + break + } + + } + return buffer.String() +} diff --git a/internal/civisibility/utils/names_test.go b/internal/civisibility/utils/names_test.go new file mode 100644 index 0000000000..fe98952392 --- /dev/null +++ b/internal/civisibility/utils/names_test.go @@ -0,0 +1,40 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package utils + +import ( + "runtime" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetModuleAndSuiteName(t *testing.T) { + pc, _, _, _ := runtime.Caller(0) // Get the program counter of this function + module, suite := GetModuleAndSuiteName(pc) + expectedModule := "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils" + expectedSuite := "names_test.go" + + assert.True(t, strings.HasPrefix(module, expectedModule)) + assert.Equal(t, expectedSuite, suite) +} + +func TestGetStacktrace(t *testing.T) { + stacktrace := GetStacktrace(0) + assert.Contains(t, stacktrace, "names_test.go") // Ensure that the current test file is part of the stack trace + assert.Contains(t, stacktrace, "TestGetStacktrace") // Ensure that the current test function is part of the stack trace +} + +func TestGetStacktraceWithSkip(t *testing.T) { + stacktrace := getStacktraceHelper() + assert.Contains(t, stacktrace, "names_test.go") + assert.NotContains(t, stacktrace, "getStacktraceHelper") // Ensure the helper function is skipped +} + +func getStacktraceHelper() string { + return GetStacktrace(1) // Skip this helper function frame +} diff --git a/internal/civisibility/utils/testdata/fixtures/codeowners/CODEOWNERS_GITHUB b/internal/civisibility/utils/testdata/fixtures/codeowners/CODEOWNERS_GITHUB new file mode 100644 index 0000000000..afbd198dcf --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/codeowners/CODEOWNERS_GITHUB @@ -0,0 +1,54 @@ +# This is a comment. +# Each line is a file pattern followed by one or more owners. + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @global-owner1 and @global-owner2 will be requested for +# review when someone opens a pull request. +* @global-owner1 @global-owner2 + +# Order is important; the last matching pattern takes the most +# precedence. When someone opens a pull request that only +# modifies JS files, only @js-owner and not the global +# owner(s) will be requested for a review. +*.js @js-owner + +# You can also use email addresses if you prefer. They'll be +# used to look up users just like we do for commit author +# emails. +*.go docs@example.com + +# Teams can be specified as code owners as well. Teams should +# be identified in the format @org/team-name. Teams must have +# explicit write access to the repository. In this example, +# the octocats team in the octo-org organization owns all .txt files. +*.txt @octo-org/octocats + +# In this example, @doctocat owns any files in the build/logs +# directory at the root of the repository and any of its +# subdirectories. +/build/logs/ @doctocat + +# The `docs/*` pattern will match files like +# `docs/getting-started.md` but not further nested files like +# `docs/build-app/troubleshooting.md`. +docs/* docs@example.com + +# In this example, @octocat owns any file in an apps directory +# anywhere in your repository. +apps/ @octocat + +# In this example, @doctocat owns any file in the `/docs` +# directory in the root of your repository and any of its +# subdirectories. +/docs/ @doctocat + +# In this example, any change inside the `/scripts` directory +# will require approval from @doctocat or @octocat. +/scripts/ @doctocat @octocat + +# In this example, @octocat owns any file in the `/apps` +# directory in the root of your repository except for the `/apps/github` +# subdirectory, as its owners are left empty. +/apps/ @octocat +/apps/github \ No newline at end of file diff --git a/internal/civisibility/utils/testdata/fixtures/codeowners/CODEOWNERS_GITLAB b/internal/civisibility/utils/testdata/fixtures/codeowners/CODEOWNERS_GITLAB new file mode 100644 index 0000000000..d0831c780d --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/codeowners/CODEOWNERS_GITLAB @@ -0,0 +1,64 @@ +# This is an example of a CODEOWNERS file. +# Lines that start with `#` are ignored. + +# app/ @commented-rule + +# Specify a default Code Owner by using a wildcard: +* @default-codeowner + +# Specify multiple Code Owners by using a tab or space: +* @multiple @code @owners + +# Rules defined later in the file take precedence over the rules +# defined before. +# For example, for all files with a filename ending in `.rb`: +*.rb @ruby-owner + +# Files with a `#` can still be accessed by escaping the pound sign: +\#file_with_pound.rb @owner-file-with-pound + +# Specify multiple Code Owners separated by spaces or tabs. +# In the following case the CODEOWNERS file from the root of the repo +# has 3 Code Owners (@multiple @code @owners): +CODEOWNERS @multiple @code @owners + +# You can use both usernames or email addresses to match +# users. Everything else is ignored. For example, this code +# specifies the `@legal` and a user with email `janedoe@gitlab.com` as the +# owner for the LICENSE file: +LICENSE @legal this_does_not_match janedoe@gitlab.com + +# Use group names to match groups, and nested groups to specify +# them as owners for a file: +README @group @group/with-nested/subgroup + +# End a path in a `/` to specify the Code Owners for every file +# nested in that directory, on any level: +/docs/ @all-docs + +# End a path in `/*` to specify Code Owners for every file in +# a directory, but not nested deeper. This code matches +# `docs/index.md` but not `docs/projects/index.md`: +/docs/* @root-docs + +# This code makes matches a `lib` directory nested anywhere in the repository: +lib/ @lib-owner + +# This code match only a `config` directory in the root of the repository: +/config/ @config-owner + +# If the path contains spaces, escape them like this: +path\ with\ spaces/ @space-owner + +# Code Owners section: +[Documentation] +ee/docs @docs +docs @docs + +[Database] +README.md @database +model/db @database + +# This section is combined with the previously defined [Documentation] section: +[DOCUMENTATION] +README.md @docs \ No newline at end of file diff --git a/internal/civisibility/utils/testdata/fixtures/providers/appveyor.json b/internal/civisibility/utils/testdata/fixtures/providers/appveyor.json new file mode 100644 index 0000000000..def208d711 --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/appveyor.json @@ -0,0 +1,555 @@ +[ + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "/foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "foo/bar", + "git.branch": "master", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "/foo/bar~", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/foo/bar~", + "git.branch": "master", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "/foo/~/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/foo/~/bar", + "git.branch": "master", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "~/foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/not-my-home/foo/bar", + "git.branch": "master", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "~foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "~foo/bar", + "git.branch": "master", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "~", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/not-my-home", + "git.branch": "master", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "/foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/foo/bar", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "/foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "origin/master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "/foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "refs/heads/master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "/foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "refs/heads/feature/one", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/one", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "/foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH": "origin/pr", + "APPVEYOR_REPO_BRANCH": "origin/master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/foo/bar", + "git.branch": "pr", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "/foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH": "refs/heads/pr", + "APPVEYOR_REPO_BRANCH": "refs/heads/master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/foo/bar", + "git.branch": "pr", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "/foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "origin/master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github", + "APPVEYOR_REPO_TAG_NAME": "origin/tags/0.1.0" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git", + "git.tag": "0.1.0" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_FOLDER": "/foo/bar", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_BRANCH": "refs/heads/master", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "APPVEYOR_REPO_PROVIDER": "github", + "APPVEYOR_REPO_TAG_NAME": "refs/heads/tags/0.1.0" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "appveyor-commit-author-email@datadoghq.com", + "git.commit.author.name": "appveyor-commit-author-name", + "git.commit.message": "appveyor-commit-message\nappveyor-commit-message-extended", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/appveyor-repo-name.git", + "git.tag": "0.1.0" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "APPVEYOR": "true", + "APPVEYOR_BUILD_ID": "appveyor-build-id", + "APPVEYOR_BUILD_NUMBER": "appveyor-pipeline-number", + "APPVEYOR_REPO_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "APPVEYOR_REPO_COMMIT_AUTHOR": "appveyor-commit-author-name", + "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL": "appveyor-commit-author-email@datadoghq.com", + "APPVEYOR_REPO_COMMIT_MESSAGE": "appveyor-commit-message", + "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED": "appveyor-commit-message-extended", + "APPVEYOR_REPO_NAME": "appveyor-repo-name", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2" + }, + { + "ci.job.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.pipeline.id": "appveyor-build-id", + "ci.pipeline.name": "appveyor-repo-name", + "ci.pipeline.number": "appveyor-pipeline-number", + "ci.pipeline.url": "https://ci.appveyor.com/project/appveyor-repo-name/builds/appveyor-build-id", + "ci.provider.name": "appveyor", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/awscodepipeline.json b/internal/civisibility/utils/testdata/fixtures/providers/awscodepipeline.json new file mode 100644 index 0000000000..6f3071331f --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/awscodepipeline.json @@ -0,0 +1,62 @@ +[ + [ + { + "CODEBUILD_BUILD_ARN": "arn:aws:codebuild:eu-north-1:12345678:build/codebuild-demo-project:b1e6661e-e4f2-4156-9ab9-82a19", + "CODEBUILD_INITIATOR": "codepipeline/test-pipeline", + "DD_ACTION_EXECUTION_ID": "35519dc3-7c45-493c-9ba6-cd78ea11f69d", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_PIPELINE_EXECUTION_ID": "bb1f15ed-fde2-494d-8e13-88785bca9cc0" + }, + { + "_dd.ci.env_vars": "{\"CODEBUILD_BUILD_ARN\":\"arn:aws:codebuild:eu-north-1:12345678:build/codebuild-demo-project:b1e6661e-e4f2-4156-9ab9-82a19\",\"DD_PIPELINE_EXECUTION_ID\":\"bb1f15ed-fde2-494d-8e13-88785bca9cc0\",\"DD_ACTION_EXECUTION_ID\":\"35519dc3-7c45-493c-9ba6-cd78ea11f69d\"}", + "ci.pipeline.id": "bb1f15ed-fde2-494d-8e13-88785bca9cc0", + "ci.provider.name": "awscodepipeline", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "CODEBUILD_INITIATOR": "lambdafunction/test-lambda", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/azurepipelines.json b/internal/civisibility/utils/testdata/fixtures/providers/azurepipelines.json new file mode 100644 index 0000000000..d072c2731c --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/azurepipelines.json @@ -0,0 +1,935 @@ +[ + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "master", + "BUILD_SOURCESDIRECTORY": "/foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "master", + "BUILD_SOURCESDIRECTORY": "/foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI": "https://azure-pipelines-server-uri.com/pull.git", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/pull.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "origin/master", + "BUILD_SOURCESDIRECTORY": "foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "foo/bar", + "git.branch": "master", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "origin/master", + "BUILD_SOURCESDIRECTORY": "/foo/bar~", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "HOME": "/not-my-home", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/foo/bar~", + "git.branch": "master", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "origin/master", + "BUILD_SOURCESDIRECTORY": "/foo/~/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "HOME": "/not-my-home", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/foo/~/bar", + "git.branch": "master", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "origin/master", + "BUILD_SOURCESDIRECTORY": "~/foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "HOME": "/not-my-home", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/not-my-home/foo/bar", + "git.branch": "master", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "origin/master", + "BUILD_SOURCESDIRECTORY": "~foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "HOME": "/not-my-home", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "~foo/bar", + "git.branch": "master", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "origin/master", + "BUILD_SOURCESDIRECTORY": "~", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "HOME": "/not-my-home", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/not-my-home", + "git.branch": "master", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "origin/master", + "BUILD_SOURCESDIRECTORY": "/foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "refs/heads/master", + "BUILD_SOURCESDIRECTORY": "/foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "refs/heads/feature/one", + "BUILD_SOURCESDIRECTORY": "/foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/one", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "origin/tags/0.1.0", + "BUILD_SOURCESDIRECTORY": "/foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/foo/bar", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git", + "git.tag": "0.1.0" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "refs/heads/tags/0.1.0", + "BUILD_SOURCESDIRECTORY": "/foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/foo/bar", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git", + "git.tag": "0.1.0" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "origin/master", + "BUILD_SOURCESDIRECTORY": "/foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_PULLREQUEST_SOURCEBRANCH": "origin/pr", + "SYSTEM_PULLREQUEST_SOURCECOMMITID": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/foo/bar", + "git.branch": "pr", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "refs/heads/master", + "BUILD_SOURCESDIRECTORY": "/foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_PULLREQUEST_SOURCEBRANCH": "refs/heads/pr", + "SYSTEM_PULLREQUEST_SOURCECOMMITID": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "SYSTEM_STAGEDISPLAYNAME": "azure-pipelines-stage-name", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.stage.name": "azure-pipelines-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "pr", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://azure-pipelines-server-uri.com/build.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEBRANCH": "refs/heads/feature/one", + "BUILD_SOURCESDIRECTORY": "/foo/bar", + "BUILD_SOURCEVERSION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBDISPLAYNAME": "azure-pipelines-job-name", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_PULLREQUEST_SOURCEBRANCH": "refs/heads/pr", + "SYSTEM_PULLREQUEST_SOURCECOMMITID": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.name": "azure-pipelines-job-name", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "ci.workspace_path": "/foo/bar", + "git.branch": "pr", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://azure-pipelines-server-uri.com/build.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://dev.azure.com/fabrikamfiber/repo", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "DD_TEST_CASE_NAME": "http-repository-url-no-git-suffix", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "https://dev.azure.com/fabrikamfiber/repo" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "ssh://host.xz:54321/path/to/repo/", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "DD_TEST_CASE_NAME": "ssh-repository-url-no-git-suffix", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "ssh://host.xz:54321/path/to/repo/" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://user:password@dev.azure.com/fabrikamfiber/repo.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "https://dev.azure.com/fabrikamfiber/repo.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://user:password@dev.azure.com:1234/fabrikamfiber/repo.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "https://dev.azure.com:1234/fabrikamfiber/repo.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://user:password@1.1.1.1:1234/fabrikamfiber/repo.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "https://1.1.1.1:1234/fabrikamfiber/repo.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://user:password@1.1.1.1:1234/fabrikamfiber/repo_with_@_yeah.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "https://1.1.1.1:1234/fabrikamfiber/repo_with_@_yeah.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "https://user@dev.azure.com/fabrikamfiber/repo.git", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "https://dev.azure.com/fabrikamfiber/repo.git" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "ssh://user@host.xz:54321/path/to/repo.git/", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "ssh://user:password@host.xz:54321/path/to/repo.git/", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ], + [ + { + "BUILD_BUILDID": "azure-pipelines-build-id", + "BUILD_DEFINITIONNAME": "azure-pipelines-name", + "BUILD_REPOSITORY_URI": "ssh://user:password@1.1.1.1:54321/path/to/repo.git/", + "BUILD_REQUESTEDFOREMAIL": "azure-pipelines-commit-author-email@datadoghq.com", + "BUILD_REQUESTEDFORID": "azure-pipelines-commit-author", + "BUILD_SOURCEVERSIONMESSAGE": "azure-pipelines-commit-message", + "SYSTEM_JOBID": "azure-pipelines-job-id", + "SYSTEM_TASKINSTANCEID": "azure-pipelines-task-id", + "SYSTEM_TEAMFOUNDATIONSERVERURI": "https://azure-pipelines-server-uri.com/", + "SYSTEM_TEAMPROJECTID": "azure-pipelines-project-id", + "TF_BUILD": "True" + }, + { + "_dd.ci.env_vars": "{\"SYSTEM_TEAMPROJECTID\":\"azure-pipelines-project-id\",\"BUILD_BUILDID\":\"azure-pipelines-build-id\",\"SYSTEM_JOBID\":\"azure-pipelines-job-id\"}", + "ci.job.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id&view=logs&j=azure-pipelines-job-id&t=azure-pipelines-task-id", + "ci.pipeline.id": "azure-pipelines-build-id", + "ci.pipeline.name": "azure-pipelines-name", + "ci.pipeline.number": "azure-pipelines-build-id", + "ci.pipeline.url": "https://azure-pipelines-server-uri.com/azure-pipelines-project-id/_build/results?buildId=azure-pipelines-build-id", + "ci.provider.name": "azurepipelines", + "git.commit.author.email": "azure-pipelines-commit-author-email@datadoghq.com", + "git.commit.author.name": "azure-pipelines-commit-author", + "git.commit.message": "azure-pipelines-commit-message", + "git.repository_url": "ssh://1.1.1.1:54321/path/to/repo.git/" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/bitbucket.json b/internal/civisibility/utils/testdata/fixtures/providers/bitbucket.json new file mode 100644 index 0000000000..bbd8cd37ea --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/bitbucket.json @@ -0,0 +1,595 @@ +[ + [ + { + "BITBUCKET_BRANCH": "master", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "/foo/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git" + } + ], + [ + { + "BITBUCKET_BRANCH": "master", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "foo/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git" + } + ], + [ + { + "BITBUCKET_BRANCH": "master", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "foo/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_GIT_SSH_ORIGIN": "git@github.com:DataDog/dummy-example.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/dummy-example.git" + } + ], + [ + { + "BITBUCKET_BRANCH": "master", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "/foo/bar~", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "/foo/bar~", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git" + } + ], + [ + { + "BITBUCKET_BRANCH": "master", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "/foo/~/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "/foo/~/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git" + } + ], + [ + { + "BITBUCKET_BRANCH": "master", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "~/foo/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "/not-my-home/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git" + } + ], + [ + { + "BITBUCKET_BRANCH": "master", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "~foo/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "~foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git" + } + ], + [ + { + "BITBUCKET_BRANCH": "master", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "~", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "/not-my-home", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git" + } + ], + [ + { + "BITBUCKET_BRANCH": "master", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "/foo/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git" + } + ], + [ + { + "BITBUCKET_BRANCH": "origin/master", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "/foo/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git" + } + ], + [ + { + "BITBUCKET_BRANCH": "refs/heads/master", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "/foo/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git" + } + ], + [ + { + "BITBUCKET_BRANCH": "refs/heads/feature/one", + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "/foo/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/one", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "/foo/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo", + "BITBUCKET_TAG": "origin/tags/0.1.0" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "/foo/bar", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_CLONE_DIR": "/foo/bar", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo", + "BITBUCKET_TAG": "refs/heads/tags/0.1.0" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "ci.workspace_path": "/foo/bar", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket-repo-url.com/repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket-repo-url.com/repo.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://bitbucket.org/DataDog/dogweb", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo", + "DD_TEST_CASE_NAME": "http-repository-url-no-git-suffix" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket.org/DataDog/dogweb" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "ssh://host.xz:54321/path/to/repo/", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo", + "DD_TEST_CASE_NAME": "ssh-repository-url-no-git-suffix" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo/" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://user:password@bitbucket.org/DataDog/dogweb.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket.org/DataDog/dogweb.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://user@bitbucket.org/DataDog/dogweb.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket.org/DataDog/dogweb.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://user:password@bitbucket.org:1234/DataDog/dogweb.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitbucket.org:1234/DataDog/dogweb.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://user:password@1.1.1.1/DataDog/dogweb.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "ssh://user@host.xz:54321/path/to/repo.git/", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ], + [ + { + "BITBUCKET_BUILD_NUMBER": "bitbucket-build-num", + "BITBUCKET_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITBUCKET_GIT_HTTP_ORIGIN": "ssh://user:password@host.xz:54321/path/to/repo.git/", + "BITBUCKET_PIPELINE_UUID": "{bitbucket-uuid}", + "BITBUCKET_REPO_FULL_NAME": "bitbucket-repo" + }, + { + "ci.job.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.pipeline.id": "bitbucket-uuid", + "ci.pipeline.name": "bitbucket-repo", + "ci.pipeline.number": "bitbucket-build-num", + "ci.pipeline.url": "https://bitbucket.org/bitbucket-repo/addon/pipelines/home#!/results/bitbucket-build-num", + "ci.provider.name": "bitbucket", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/bitrise.json b/internal/civisibility/utils/testdata/fixtures/providers/bitrise.json new file mode 100644 index 0000000000..579ace5d2c --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/bitrise.json @@ -0,0 +1,694 @@ +[ + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/bar", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "git@github.com:DataDog/dummy-example.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/bar", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/dummy-example.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "origin/master", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "origin/master", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "foo/bar", + "git.branch": "master", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "origin/master", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "/foo/bar~", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/bar~", + "git.branch": "master", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "origin/master", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "/foo/~/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/~/bar", + "git.branch": "master", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "origin/master", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "~/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/not-my-home/foo/bar", + "git.branch": "master", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "origin/master", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "~foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "~foo/bar", + "git.branch": "master", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "origin/master", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "~", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/not-my-home", + "git.branch": "master", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "refs/heads/master", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "refs/heads/feature/one", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/one", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "origin/tags/0.1.0", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_GIT_TAG": "origin/tags/0.1.0", + "BITRISE_SOURCE_DIR": "/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/bar", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "refs/heads/tags/0.1.0", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_GIT_TAG": "refs/heads/tags/0.1.0", + "BITRISE_SOURCE_DIR": "/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://bitrise-build-url.com/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/bar", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://bitrise-build-url.com/repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "origin/master", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "http://hostname.com/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "origin/master", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "git@hostname.com:org/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@hostname.com:org/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_BRANCH": "origin/notmaster", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_SOURCE_DIR": "/foo/bar", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "git@hostname.com:org/repo.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "ci.workspace_path": "/foo/bar", + "git.branch": "notmaster", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@hostname.com:org/repo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "DD_TEST_CASE_NAME": "http-repository-url-no-git-suffix", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://github.com/DataDog/dogweb" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "DD_TEST_CASE_NAME": "ssh-repository-url-no-git-suffix", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "ssh://host.xz:54321/path/to/repo/" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo/" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://user:password@github.com/DataDog/dogweb.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://user@github.com/DataDog/dogweb.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://user:password@github.com:1234/DataDog/dogweb.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://user:password@1.1.1.1/DataDog/dogweb.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "ssh://user@host.xz:54321/path/to/repo.git/" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ], + [ + { + "BITRISE_BUILD_NUMBER": "bitrise-pipeline-number", + "BITRISE_BUILD_SLUG": "bitrise-pipeline-id", + "BITRISE_BUILD_URL": "https://bitrise-build-url.com//", + "BITRISE_GIT_MESSAGE": "bitrise-git-commit-message", + "BITRISE_TRIGGERED_WORKFLOW_ID": "bitrise-pipeline-name", + "GIT_CLONE_COMMIT_HASH": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_REPOSITORY_URL": "ssh://user:password@host.xz:54321/path/to/repo.git/" + }, + { + "ci.pipeline.id": "bitrise-pipeline-id", + "ci.pipeline.name": "bitrise-pipeline-name", + "ci.pipeline.number": "bitrise-pipeline-number", + "ci.pipeline.url": "https://bitrise-build-url.com//", + "ci.provider.name": "bitrise", + "git.commit.message": "bitrise-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/buddy.json b/internal/civisibility/utils/testdata/fixtures/providers/buddy.json new file mode 100644 index 0000000000..9c64a199d1 --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/buddy.json @@ -0,0 +1,483 @@ +[ + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://github.com/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "my-name-is-rotag/fix-original-bug", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://github.com/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "my-name-is-rotag/fix-original-bug", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "refs/heads/feature/one", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "refs/heads/tags/0.2.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://github.com/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "feature/one", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/buddyworks/my-project.git", + "git.tag": "0.2.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://github.com/buddyworks/my-project.git", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "user-supplied-tag" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "user-supplied-tag" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://github.com/buddyworks/my-project.git", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://github.com/buddyworks/my-project", + "DD_TEST_CASE_NAME": "http-repository-url-no-git-suffix" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/buddyworks/my-project", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "ssh://host.xz:54321/path/to/repo/", + "DD_TEST_CASE_NAME": "ssh-repository-url-no-git-suffix" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo/", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user:password@github.com/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user@github.com/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user:password@github.com:1234/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user:password@1.1.1.1/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user:password@1.1.1.1:1234/buddyworks/my-project.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/buddyworks/my-project.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "https://user:password@1.1.1.1:1234/buddyworks/my-project_with_@_yeah.git" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/buddyworks/my-project_with_@_yeah.git", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "ssh://user@host.xz:54321/path/to/repo.git/" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/", + "git.tag": "v1.0" + } + ], + [ + { + "BUDDY": "true", + "BUDDY_EXECUTION_BRANCH": "master", + "BUDDY_EXECUTION_ID": "buddy-execution-id", + "BUDDY_EXECUTION_REVISION": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL": "mikebenson@buddy.works", + "BUDDY_EXECUTION_REVISION_COMMITTER_NAME": "Mike Benson", + "BUDDY_EXECUTION_REVISION_MESSAGE": "Create buddy.yml", + "BUDDY_EXECUTION_TAG": "v1.0", + "BUDDY_EXECUTION_URL": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "BUDDY_PIPELINE_ID": "456", + "BUDDY_PIPELINE_NAME": "Deploy to Production", + "BUDDY_SCM_URL": "ssh://user:password@host.xz:54321/path/to/repo.git/" + }, + { + "ci.pipeline.id": "456/buddy-execution-id", + "ci.pipeline.name": "Deploy to Production", + "ci.pipeline.number": "buddy-execution-id", + "ci.pipeline.url": "https://app.buddy.works/myworkspace/my-project/pipelines/pipeline/456/execution/5d9dc42c422f5a268b389d08", + "ci.provider.name": "buddy", + "git.branch": "master", + "git.commit.committer.email": "mikebenson@buddy.works", + "git.commit.committer.name": "Mike Benson", + "git.commit.message": "Create buddy.yml", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/", + "git.tag": "v1.0" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/buildkite.json b/internal/civisibility/utils/testdata/fixtures/providers/buildkite.json new file mode 100644 index 0000000000..86ff170deb --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/buildkite.json @@ -0,0 +1,1022 @@ +[ + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "foo/bar", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar~", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar~", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/~/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/~/bar", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "~/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/not-my-home/foo/bar", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "~foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "~foo/bar", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "~", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/not-my-home", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://user@hostname.com/repo.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://user%E2%82%AC@hostname.com/repo.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://user:pwd@hostname.com/repo.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "git@hostname.com:org/repo.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@hostname.com:org/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "origin/master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "refs/heads/master", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "refs/heads/feature/one", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/one", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "0.1.0" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "origin/tags/0.1.0" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_CHECKOUT_PATH": "/foo/bar", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "http://hostname.com/repo.git", + "BUILDKITE_TAG": "refs/heads/tags/0.1.0" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "ci.workspace_path": "/foo/bar", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_TAG": "", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_TAG": "", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://github.com/DataDog/dogweb", + "BUILDKITE_TAG": "", + "DD_TEST_CASE_NAME": "http-repository-url-no-git-suffix" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "ssh://host.xz:54321/path/to/repo/", + "BUILDKITE_TAG": "", + "DD_TEST_CASE_NAME": "ssh-repository-url-no-git-suffix" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo/" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://user:password@github.com/DataDog/dogweb.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://user@github.com/DataDog/dogweb.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://user:password@github.com:1234/DataDog/dogweb.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://user:password@1.1.1.1/DataDog/dogweb.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "ssh://user@host.xz:54321/path/to/repo.git/", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_REPO": "ssh://user:password@host.xz:54321/path/to/repo.git/", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ], + [ + { + "BUILDKITE": "true", + "BUILDKITE_AGENT_ID": "1a222222-e999-3636-8ddd-802222222222", + "BUILDKITE_AGENT_META_DATA_MYOTHERTAG": "my-other-value", + "BUILDKITE_AGENT_META_DATA_MYTAG": "my-value", + "BUILDKITE_BRANCH": "", + "BUILDKITE_BUILD_AUTHOR": "buildkite-git-commit-author-name", + "BUILDKITE_BUILD_AUTHOR_EMAIL": "buildkite-git-commit-author-email@datadoghq.com", + "BUILDKITE_BUILD_ID": "buildkite-pipeline-id", + "BUILDKITE_BUILD_NUMBER": "buildkite-pipeline-number", + "BUILDKITE_BUILD_URL": "https://buildkite-build-url.com", + "BUILDKITE_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "BUILDKITE_JOB_ID": "buildkite-job-id", + "BUILDKITE_MESSAGE": "buildkite-git-commit-message", + "BUILDKITE_PIPELINE_SLUG": "buildkite-pipeline-name", + "BUILDKITE_TAG": "" + }, + { + "_dd.ci.env_vars": "{\"BUILDKITE_BUILD_ID\":\"buildkite-pipeline-id\",\"BUILDKITE_JOB_ID\":\"buildkite-job-id\"}", + "ci.job.url": "https://buildkite-build-url.com#buildkite-job-id", + "ci.node.labels": "[\"mytag:my-value\",\"myothertag:my-other-value\"]", + "ci.node.name": "1a222222-e999-3636-8ddd-802222222222", + "ci.pipeline.id": "buildkite-pipeline-id", + "ci.pipeline.name": "buildkite-pipeline-name", + "ci.pipeline.number": "buildkite-pipeline-number", + "ci.pipeline.url": "https://buildkite-build-url.com", + "ci.provider.name": "buildkite", + "git.commit.author.email": "buildkite-git-commit-author-email@datadoghq.com", + "git.commit.author.name": "buildkite-git-commit-author-name", + "git.commit.message": "buildkite-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/circleci.json b/internal/civisibility/utils/testdata/fixtures/providers/circleci.json new file mode 100644 index 0000000000..2a697af349 --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/circleci.json @@ -0,0 +1,754 @@ +[ + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://circleci-build-url.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "foo/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://circleci-build-url.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/bar~" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/bar~", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://circleci-build-url.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/~/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/~/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://circleci-build-url.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "~/foo/bar", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/not-my-home/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://circleci-build-url.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "~foo/bar", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "~foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://circleci-build-url.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "~", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/not-my-home", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://circleci-build-url.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "refs/heads/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://circleci-build-url.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "refs/heads/feature/one", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/one", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://circleci-build-url.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/tags/0.1.0", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_TAG": "origin/tags/0.1.0", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/bar", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://circleci-build-url.com/repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "refs/heads/tags/0.1.0", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://circleci-build-url.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_TAG": "refs/heads/tags/0.1.0", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/bar", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://circleci-build-url.com/repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "http://hostname.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "http://user@hostname.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "http://user%E2%82%AC@hostname.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "http://user:pwd@hostname.com/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BRANCH": "origin/master", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "git@hostname.com:org/repo.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "CIRCLE_WORKING_DIRECTORY": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@hostname.com:org/repo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://github.com/DataDog/dogweb", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "DD_TEST_CASE_NAME": "http-repository-url-no-git-suffix" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "ssh://host.xz:54321/path/to/repo/", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id", + "DD_TEST_CASE_NAME": "ssh-repository-url-no-git-suffix" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo/" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://user:password@github.com/DataDog/dogweb.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://user@github.com/DataDog/dogweb.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://user:password@github.com:1234/DataDog/dogweb.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://user:password@1.1.1.1/DataDog/dogweb.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "ssh://user@host.xz:54321/path/to/repo.git/", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ], + [ + { + "CIRCLECI": "circleCI", + "CIRCLE_BUILD_NUM": "circleci-pipeline-number", + "CIRCLE_BUILD_URL": "https://circleci-build-url.com/", + "CIRCLE_JOB": "circleci-job-name", + "CIRCLE_PROJECT_REPONAME": "circleci-pipeline-name", + "CIRCLE_REPOSITORY_URL": "ssh://user:password@host.xz:54321/path/to/repo.git/", + "CIRCLE_SHA1": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CIRCLE_WORKFLOW_ID": "circleci-pipeline-id" + }, + { + "_dd.ci.env_vars": "{\"CIRCLE_WORKFLOW_ID\":\"circleci-pipeline-id\",\"CIRCLE_BUILD_NUM\":\"circleci-pipeline-number\"}", + "ci.job.name": "circleci-job-name", + "ci.job.url": "https://circleci-build-url.com/", + "ci.pipeline.id": "circleci-pipeline-id", + "ci.pipeline.name": "circleci-pipeline-name", + "ci.pipeline.url": "https://app.circleci.com/pipelines/workflows/circleci-pipeline-id", + "ci.provider.name": "circleci", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/codefresh.json b/internal/civisibility/utils/testdata/fixtures/providers/codefresh.json new file mode 100644 index 0000000000..d719df1059 --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/codefresh.json @@ -0,0 +1,162 @@ +[ + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh" + } + ], + [ + { + "CF_BRANCH": "origin/master", + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.branch": "master" + } + ], + [ + { + "CF_BRANCH": "refs/heads/feature/one", + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.branch": "feature/one" + } + ], + [ + { + "CF_BRANCH": "origin/tags/0.1.0", + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.tag": "0.1.0" + } + ], + [ + { + "CF_BRANCH": "refs/heads/tags/0.1.0", + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.tag": "0.1.0" + } + ], + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "CF_BUILD_ID": "6410367cee516146a4c4c66e", + "CF_BUILD_URL": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "CF_PIPELINE_NAME": "My simple project/Example Java Project Pipeline", + "CF_STEP_NAME": "mah-job-name", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2" + }, + { + "_dd.ci.env_vars": "{\"CF_BUILD_ID\":\"6410367cee516146a4c4c66e\"}", + "ci.job.name": "mah-job-name", + "ci.pipeline.id": "6410367cee516146a4c4c66e", + "ci.pipeline.name": "My simple project/Example Java Project Pipeline", + "ci.pipeline.url": "https://g.codefresh.io/build/6410367cee516146a4c4c66e", + "ci.provider.name": "codefresh", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/github.json b/internal/civisibility/utils/testdata/fixtures/providers/github.json new file mode 100644 index 0000000000..3dd5ac62d5 --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/github.json @@ -0,0 +1,714 @@ +[ + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://ghenterprise.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://ghenterprise.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://ghenterprise.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://ghenterprise.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://ghenterprise.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/bar~" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/bar~", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/~/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/~/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "~/foo/bar", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/not-my-home/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "~foo/bar", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "~foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "~", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/not-my-home", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "origin/master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "refs/heads/master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "refs/heads/feature/one", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/one", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "origin/tags/0.1.0", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/bar", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "refs/heads/tags/0.1.0", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/bar", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_HEAD_REF": "origin/other", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "origin/master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/bar", + "git.branch": "other", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_HEAD_REF": "refs/heads/other", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "refs/heads/master", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/bar", + "git.branch": "other", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_HEAD_REF": "refs/heads/feature/other", + "GITHUB_JOB": "github-job-name", + "GITHUB_REF": "refs/heads/feature/one", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/other", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "foo/bar", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2", + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name", + "GITHUB_WORKSPACE": "foo/bar" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "ci.workspace_path": "foo/bar", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://user:password@github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://user@github.com", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://user:password@github.com:1234", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://github.com:1234\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://github.com:1234/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://github.com:1234/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://user:password@1.1.1.1", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://1.1.1.1\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://1.1.1.1/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://1.1.1.1/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/ghactions-repo.git" + } + ], + [ + { + "GITHUB_ACTION": "run", + "GITHUB_JOB": "github-job-name", + "GITHUB_REPOSITORY": "ghactions-repo", + "GITHUB_RUN_ATTEMPT": "ghactions-run-attempt", + "GITHUB_RUN_ID": "ghactions-pipeline-id", + "GITHUB_RUN_NUMBER": "ghactions-pipeline-number", + "GITHUB_SERVER_URL": "https://user:password@1.1.1.1:1234", + "GITHUB_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GITHUB_WORKFLOW": "ghactions-pipeline-name" + }, + { + "_dd.ci.env_vars": "{\"GITHUB_SERVER_URL\":\"https://1.1.1.1:1234\",\"GITHUB_REPOSITORY\":\"ghactions-repo\",\"GITHUB_RUN_ID\":\"ghactions-pipeline-id\",\"GITHUB_RUN_ATTEMPT\":\"ghactions-run-attempt\"}", + "ci.job.name": "github-job-name", + "ci.job.url": "https://1.1.1.1:1234/ghactions-repo/commit/b9f0fb3fdbb94c9d24b2c75b49663122a529e123/checks", + "ci.pipeline.id": "ghactions-pipeline-id", + "ci.pipeline.name": "ghactions-pipeline-name", + "ci.pipeline.number": "ghactions-pipeline-number", + "ci.pipeline.url": "https://1.1.1.1:1234/ghactions-repo/actions/runs/ghactions-pipeline-id/attempts/ghactions-run-attempt", + "ci.provider.name": "github", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/ghactions-repo.git" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/gitlab.json b/internal/civisibility/utils/testdata/fixtures/providers/gitlab.json new file mode 100644 index 0000000000..24882c1e85 --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/gitlab.json @@ -0,0 +1,1047 @@ +[ + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar~", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar~", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/~/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/~/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "~/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI": "gitlab", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/not-my-home/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "~foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI": "gitlab", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "~foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "~", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI": "gitlab", + "HOME": "/not-my-home", + "USERPROFILE": "/not-my-home" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/not-my-home", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "refs/heads/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "refs/heads/feature/one", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/one", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TAG": "origin/tags/0.1.0", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TAG": "refs/heads/tags/0.1.0", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TAG": "0.1.0", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://hostname.com/repo", + "DD_TEST_CASE_NAME": "http-repository-url-no-git-suffix", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "ssh://host.xz:54321/path/to/repo/", + "DD_TEST_CASE_NAME": "ssh-repository-url-no-git-suffix", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo/" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://hostname.com/repo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://user@hostname.com/repo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://user%E2%82%AC@hostname.com/repo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://user:pwd@hostname.com/repo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "git@hostname.com:org/repo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@hostname.com:org/repo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://user:pwd@hostname.com:1234/repo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com:1234/repo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://user:pwd@1.1.1.1/repo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://1.1.1.1/repo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://user:pwd@1.1.1.1:1234/repo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://1.1.1.1:1234/repo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "http://user:pwd@1.1.1.1:1234/repo_with_@_yeah.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://1.1.1.1:1234/repo_with_@_yeah.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ], + [ + { + "CI_COMMIT_AUTHOR": "John Doe ", + "CI_COMMIT_MESSAGE": "gitlab-git-commit-message", + "CI_COMMIT_REF_NAME": "origin/master", + "CI_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "CI_COMMIT_TIMESTAMP": "2021-07-21T11:43:07-04:00", + "CI_JOB_ID": "gitlab-job-id", + "CI_JOB_NAME": "gitlab-job-name", + "CI_JOB_STAGE": "gitlab-stage-name", + "CI_JOB_URL": "https://gitlab.com/job", + "CI_PIPELINE_ID": "gitlab-pipeline-id", + "CI_PIPELINE_IID": "gitlab-pipeline-number", + "CI_PIPELINE_URL": "https://foo/repo/-/pipelines/1234", + "CI_PROJECT_DIR": "/foo/bar", + "CI_PROJECT_PATH": "gitlab-pipeline-name", + "CI_PROJECT_URL": "https://gitlab.com/repo", + "CI_REPOSITORY_URL": "https://gitlab.com/repo/myrepo.git", + "CI_RUNNER_ID": "9393040", + "CI_RUNNER_TAGS": "[\"arch:arm64\",\"linux\"]", + "GITLAB_CI": "gitlab" + }, + { + "_dd.ci.env_vars": "{\"CI_PROJECT_URL\":\"https://gitlab.com/repo\",\"CI_PIPELINE_ID\":\"gitlab-pipeline-id\",\"CI_JOB_ID\":\"gitlab-job-id\"}", + "ci.job.name": "gitlab-job-name", + "ci.job.url": "https://gitlab.com/job", + "ci.node.labels": "[\"arch:arm64\",\"linux\"]", + "ci.node.name": "9393040", + "ci.pipeline.id": "gitlab-pipeline-id", + "ci.pipeline.name": "gitlab-pipeline-name", + "ci.pipeline.number": "gitlab-pipeline-number", + "ci.pipeline.url": "https://foo/repo/-/pipelines/1234", + "ci.provider.name": "gitlab", + "ci.stage.name": "gitlab-stage-name", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.author.date": "2021-07-21T11:43:07-04:00", + "git.commit.author.email": "john@doe.com", + "git.commit.author.name": "John Doe", + "git.commit.message": "gitlab-git-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://gitlab.com/repo/myrepo.git" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/jenkins.json b/internal/civisibility/utils/testdata/fixtures/providers/jenkins.json new file mode 100644 index 0000000000..38d448747b --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/jenkins.json @@ -0,0 +1,904 @@ +[ + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://jenkins.com/repo/sample.git", + "GIT_URL_2": "https://jenkins.com/repo/otherSample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar~" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar~", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/~/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/~/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "HOME": "/not-my-home", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "USERPROFILE": "/not-my-home", + "WORKSPACE": "~/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/not-my-home/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "HOME": "/not-my-home", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "USERPROFILE": "/not-my-home", + "WORKSPACE": "~foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "~foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "HOME": "/not-my-home", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "USERPROFILE": "/not-my-home", + "WORKSPACE": "~" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/not-my-home", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "refs/heads/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName/master", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "refs/heads/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName/another", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName/another", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "refs/heads/feature/one", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName/feature/one", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/one", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "refs/heads/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName/KEY1=VALUE1,KEY2=VALUE2", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "refs/heads/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName/KEY1=VALUE1,KEY2=VALUE2/master", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/tags/0.1.0", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git", + "git.tag": "0.1.0" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "refs/heads/tags/0.1.0", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "https://jenkins.com/repo/sample.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://jenkins.com/repo/sample.git", + "git.tag": "0.1.0" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "http://hostname.com/repo.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "http://user@hostname.com/repo.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "http://user%E2%82%AC@hostname.com/repo.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "http://user:pwd@hostname.com/repo.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "http://hostname.com/repo.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_BRANCH": "origin/master", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL": "git@hostname.com:org/repo.git", + "JENKINS_URL": "jenkins", + "JOB_NAME": "jobName", + "JOB_URL": "https://jenkins.com/job", + "WORKSPACE": "/foo/bar" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.name": "jobName", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@hostname.com:org/repo.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "DD_TEST_CASE_NAME": "http-repository-url-no-git-suffix", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://github.com/DataDog/dogweb", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "DD_TEST_CASE_NAME": "ssh-repository-url-no-git-suffix", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "ssh://host.xz:54321/path/to/repo/", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo/" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://user:password@github.com/DataDog/dogweb.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://user@github.com/DataDog/dogweb.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://user:password@github.com:1234/DataDog/dogweb.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://user:password@1.1.1.1/DataDog/dogweb.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "ssh://user@host.xz:54321/path/to/repo.git/", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "GIT_URL_1": "ssh://user:password@host.xz:54321/path/to/repo.git/", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ], + [ + { + "BUILD_NUMBER": "jenkins-pipeline-number", + "BUILD_TAG": "jenkins-pipeline-id", + "BUILD_URL": "https://jenkins.com/pipeline", + "DD_CUSTOM_TRACE_ID": "jenkins-custom-trace-id", + "GIT_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "JENKINS_URL": "jenkins", + "JOB_URL": "https://jenkins.com/job", + "NODE_LABELS": "built-in linux", + "NODE_NAME": "my-node" + }, + { + "_dd.ci.env_vars": "{\"DD_CUSTOM_TRACE_ID\":\"jenkins-custom-trace-id\"}", + "ci.node.labels": "[\"built-in\",\"linux\"]", + "ci.node.name": "my-node", + "ci.pipeline.id": "jenkins-pipeline-id", + "ci.pipeline.number": "jenkins-pipeline-number", + "ci.pipeline.url": "https://jenkins.com/pipeline", + "ci.provider.name": "jenkins", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/teamcity.json b/internal/civisibility/utils/testdata/fixtures/providers/teamcity.json new file mode 100644 index 0000000000..086c1c16de --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/teamcity.json @@ -0,0 +1,78 @@ +[ + [ + { + "BUILD_URL": "https://teamcity.com/repo", + "TEAMCITY_BUILDCONF_NAME": "Test 1", + "TEAMCITY_VERSION": "2022.10 (build 116751)" + }, + { + "ci.job.name": "Test 1", + "ci.job.url": "https://teamcity.com/repo", + "ci.provider.name": "teamcity" + } + ], + [ + { + "BUILD_URL": "https://teamcity.com/repo", + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "TEAMCITY_BUILDCONF_NAME": "Test 1", + "TEAMCITY_VERSION": "2022.10 (build 116751)" + }, + { + "ci.job.name": "Test 1", + "ci.job.url": "https://teamcity.com/repo", + "ci.provider.name": "teamcity", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "BUILD_URL": "https://teamcity.com/repo", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2", + "TEAMCITY_BUILDCONF_NAME": "Test 1", + "TEAMCITY_VERSION": "2022.10 (build 116751)" + }, + { + "ci.job.name": "Test 1", + "ci.job.url": "https://teamcity.com/repo", + "ci.provider.name": "teamcity", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/travisci.json b/internal/civisibility/utils/testdata/fixtures/providers/travisci.json new file mode 100644 index 0000000000..2131938546 --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/travisci.json @@ -0,0 +1,525 @@ +[ + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/tags/0.1.0", + "TRAVIS_BUILD_DIR": "/foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo", + "TRAVIS_TAG": "origin/tags/0.1.0" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/foo/bar", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "refs/heads/tags/0.1.0", + "TRAVIS_BUILD_DIR": "/foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo", + "TRAVIS_TAG": "refs/heads/tags/0.1.0" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/foo/bar", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/master", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "git.branch": "master", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/master", + "TRAVIS_BUILD_DIR": "foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "foo/bar", + "git.branch": "master", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/master", + "TRAVIS_BUILD_DIR": "/foo/bar~", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/foo/bar~", + "git.branch": "master", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/master", + "TRAVIS_BUILD_DIR": "/foo/~/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/foo/~/bar", + "git.branch": "master", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "HOME": "/not-my-home", + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/master", + "TRAVIS_BUILD_DIR": "~/foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo", + "USERPROFILE": "/not-my-home" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/not-my-home/foo/bar", + "git.branch": "master", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/master", + "TRAVIS_BUILD_DIR": "~foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "~foo/bar", + "git.branch": "master", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "HOME": "/not-my-home", + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/master", + "TRAVIS_BUILD_DIR": "~", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo", + "USERPROFILE": "/not-my-home" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/not-my-home", + "git.branch": "master", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/master", + "TRAVIS_BUILD_DIR": "/foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "refs/heads/master", + "TRAVIS_BUILD_DIR": "/foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "refs/heads/feature/one", + "TRAVIS_BUILD_DIR": "/foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/one", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/other", + "TRAVIS_BUILD_DIR": "/foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_PULL_REQUEST_BRANCH": "origin/master", + "TRAVIS_PULL_REQUEST_SLUG": "user/repo", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/other", + "TRAVIS_BUILD_DIR": "/foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_PULL_REQUEST_BRANCH": "refs/heads/master", + "TRAVIS_PULL_REQUEST_SLUG": "user/repo", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/foo/bar", + "git.branch": "master", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/other", + "TRAVIS_BUILD_DIR": "/foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_PULL_REQUEST_BRANCH": "refs/heads/feature/one", + "TRAVIS_PULL_REQUEST_SLUG": "user/repo", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/foo/bar", + "git.branch": "feature/one", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git" + } + ], + [ + { + "DD_GIT_BRANCH": "user-supplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "TRAVIS": "travisCI", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "git.branch": "user-supplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2", + "TRAVIS": "travisCI", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ], + [ + { + "TRAVIS": "travisCI", + "TRAVIS_BRANCH": "origin/tags/0.1.0", + "TRAVIS_BUILD_DIR": "/foo/bar", + "TRAVIS_BUILD_ID": "travis-pipeline-id", + "TRAVIS_BUILD_NUMBER": "travis-pipeline-number", + "TRAVIS_BUILD_WEB_URL": "https://travisci.com/pipeline", + "TRAVIS_COMMIT": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "TRAVIS_COMMIT_MESSAGE": "travis-commit-message", + "TRAVIS_JOB_WEB_URL": "https://travisci.com/job", + "TRAVIS_REPO_SLUG": "user/repo", + "TRAVIS_TAG": "origin/tags/0.1.0" + }, + { + "ci.job.url": "https://travisci.com/job", + "ci.pipeline.id": "travis-pipeline-id", + "ci.pipeline.name": "user/repo", + "ci.pipeline.number": "travis-pipeline-number", + "ci.pipeline.url": "https://travisci.com/pipeline", + "ci.provider.name": "travisci", + "ci.workspace_path": "/foo/bar", + "git.commit.message": "travis-commit-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/user/repo.git", + "git.tag": "0.1.0" + } + ] +] diff --git a/internal/civisibility/utils/testdata/fixtures/providers/usersupplied.json b/internal/civisibility/utils/testdata/fixtures/providers/usersupplied.json new file mode 100644 index 0000000000..f07f4e3740 --- /dev/null +++ b/internal/civisibility/utils/testdata/fixtures/providers/usersupplied.json @@ -0,0 +1,400 @@ +[ + [ + { + "DD_GIT_BRANCH": "usersupplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "git.branch": "usersupplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "DD_GIT_BRANCH": "origin/usersupplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "git.branch": "usersupplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "DD_GIT_BRANCH": "refs/heads/usersupplied-branch", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "git.branch": "usersupplied-branch", + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git" + } + ], + [ + { + "DD_GIT_BRANCH": "origin/tags/0.1.0", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "DD_GIT_BRANCH": "refs/heads/tags/0.1.0", + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.1.0" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "git@github.com:DataDog/userrepo.git", + "DD_GIT_TAG": "0.0.2" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "git@github.com:DataDog/userrepo.git", + "git.tag": "0.0.2" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://github.com/DataDog/dogweb", + "DD_TEST_CASE_NAME": "http-repository-url-no-git-suffix" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "ssh://host.xz:54321/path/to/repo/", + "DD_TEST_CASE_NAME": "ssh-repository-url-no-git-suffix" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo/" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://user:password@github.com/DataDog/dogweb.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://user@github.com/DataDog/dogweb.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com/DataDog/dogweb.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://user:password@github.com:1234/DataDog/dogweb.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://github.com:1234/DataDog/dogweb.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1/DataDog/dogweb.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1/DataDog/dogweb.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "https://user:password@1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "https://1.1.1.1:1234/DataDog/dogweb_with_@_yeah.git" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "ssh://user@host.xz:54321/path/to/repo.git/" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ], + [ + { + "DD_GIT_COMMIT_AUTHOR_DATE": "usersupplied-authordate", + "DD_GIT_COMMIT_AUTHOR_EMAIL": "usersupplied-authoremail", + "DD_GIT_COMMIT_AUTHOR_NAME": "usersupplied-authorname", + "DD_GIT_COMMIT_COMMITTER_DATE": "usersupplied-comitterdate", + "DD_GIT_COMMIT_COMMITTER_EMAIL": "usersupplied-comitteremail", + "DD_GIT_COMMIT_COMMITTER_NAME": "usersupplied-comittername", + "DD_GIT_COMMIT_MESSAGE": "usersupplied-message", + "DD_GIT_COMMIT_SHA": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "DD_GIT_REPOSITORY_URL": "ssh://user:password@host.xz:54321/path/to/repo.git/" + }, + { + "git.commit.author.date": "usersupplied-authordate", + "git.commit.author.email": "usersupplied-authoremail", + "git.commit.author.name": "usersupplied-authorname", + "git.commit.committer.date": "usersupplied-comitterdate", + "git.commit.committer.email": "usersupplied-comitteremail", + "git.commit.committer.name": "usersupplied-comittername", + "git.commit.message": "usersupplied-message", + "git.commit.sha": "b9f0fb3fdbb94c9d24b2c75b49663122a529e123", + "git.repository_url": "ssh://host.xz:54321/path/to/repo.git/" + } + ] +] diff --git a/internal/datastreams/processor.go b/internal/datastreams/processor.go index 81900c2cb0..654c72dbb3 100644 --- a/internal/datastreams/processor.go +++ b/internal/datastreams/processor.go @@ -27,9 +27,8 @@ import ( ) const ( - bucketDuration = time.Second * 10 - loadAgentFeaturesInterval = time.Second * 30 - defaultServiceName = "unnamed-go-service" + bucketDuration = time.Second * 10 + defaultServiceName = "unnamed-go-service" ) // use the same gamma and index offset as the Datadog backend, to avoid doing any conversions in @@ -191,9 +190,7 @@ type Processor struct { service string version string // used for tests - timeSource func() time.Time - disableStatsFlushing uint32 - getAgentSupportsDataStreams func() bool + timeSource func() time.Time } func (p *Processor) time() time.Time { @@ -203,25 +200,23 @@ func (p *Processor) time() time.Time { return time.Now() } -func NewProcessor(statsd internal.StatsdClient, env, service, version string, agentURL *url.URL, httpClient *http.Client, getAgentSupportsDataStreams func() bool) *Processor { +func NewProcessor(statsd internal.StatsdClient, env, service, version string, agentURL *url.URL, httpClient *http.Client) *Processor { if service == "" { service = defaultServiceName } p := &Processor{ - tsTypeCurrentBuckets: make(map[int64]bucket), - tsTypeOriginBuckets: make(map[int64]bucket), - hashCache: newHashCache(), - in: newFastQueue(), - stopped: 1, - statsd: statsd, - env: env, - service: service, - version: version, - transport: newHTTPTransport(agentURL, httpClient), - timeSource: time.Now, - getAgentSupportsDataStreams: getAgentSupportsDataStreams, - } - p.updateAgentSupportsDataStreams(getAgentSupportsDataStreams()) + tsTypeCurrentBuckets: make(map[int64]bucket), + tsTypeOriginBuckets: make(map[int64]bucket), + hashCache: newHashCache(), + in: newFastQueue(), + stopped: 1, + statsd: statsd, + env: env, + service: service, + version: version, + transport: newHTTPTransport(agentURL, httpClient), + timeSource: time.Now, + } return p } @@ -345,20 +340,14 @@ func (p *Processor) Start() { } p.stop = make(chan struct{}) p.flushRequest = make(chan chan<- struct{}) - p.wg.Add(2) go p.reportStats() + p.wg.Add(1) go func() { defer p.wg.Done() tick := time.NewTicker(bucketDuration) defer tick.Stop() p.run(tick.C) }() - go func() { - defer p.wg.Done() - tick := time.NewTicker(loadAgentFeaturesInterval) - defer tick.Stop() - p.runLoadAgentFeatures(tick.C) - }() } // Flush triggers a flush and waits for it to complete. @@ -508,28 +497,3 @@ func (p *Processor) TrackKafkaHighWatermarkOffset(_ string, topic string, partit atomic.AddInt64(&p.stats.dropped, 1) } } - -func (p *Processor) runLoadAgentFeatures(tick <-chan time.Time) { - for { - select { - case <-tick: - p.updateAgentSupportsDataStreams(p.getAgentSupportsDataStreams()) - case <-p.stop: - return - } - } -} - -func (p *Processor) updateAgentSupportsDataStreams(agentSupportsDataStreams bool) { - var disableStatsFlushing uint32 - if !agentSupportsDataStreams { - disableStatsFlushing = 1 - } - if atomic.SwapUint32(&p.disableStatsFlushing, disableStatsFlushing) != disableStatsFlushing { - if agentSupportsDataStreams { - log.Info("Detected agent upgrade. Turning on Data Streams Monitoring.") - } else { - log.Warn("Turning off Data Streams Monitoring. Upgrade your agent to 7.34+") - } - } -} diff --git a/internal/datastreams/processor_test.go b/internal/datastreams/processor_test.go index 19ef0d926f..1e0418a2d8 100644 --- a/internal/datastreams/processor_test.go +++ b/internal/datastreams/processor_test.go @@ -34,7 +34,7 @@ func buildSketch(values ...float64) []byte { } func TestProcessor(t *testing.T) { - p := NewProcessor(nil, "env", "service", "v1", &url.URL{Scheme: "http", Host: "agent-address"}, nil, func() bool { return true }) + p := NewProcessor(nil, "env", "service", "v1", &url.URL{Scheme: "http", Host: "agent-address"}, nil) tp1 := time.Now().Truncate(bucketDuration) tp2 := tp1.Add(time.Minute) @@ -217,7 +217,7 @@ func TestSetCheckpoint(t *testing.T) { } func TestKafkaLag(t *testing.T) { - p := NewProcessor(nil, "env", "service", "v1", &url.URL{Scheme: "http", Host: "agent-address"}, nil, func() bool { return true }) + p := NewProcessor(nil, "env", "service", "v1", &url.URL{Scheme: "http", Host: "agent-address"}, nil) tp1 := time.Now() p.addKafkaOffset(kafkaOffset{offset: 1, topic: "topic1", partition: 1, group: "group1", offsetType: commitOffset}) p.addKafkaOffset(kafkaOffset{offset: 10, topic: "topic2", partition: 1, group: "group1", offsetType: commitOffset}) @@ -264,7 +264,7 @@ func BenchmarkSetCheckpoint(b *testing.B) { client := &http.Client{ Transport: &noOpTransport{}, } - p := NewProcessor(&statsd.NoOpClient{}, "env", "service", "v1", &url.URL{Scheme: "http", Host: "agent-address"}, client, func() bool { return true }) + p := NewProcessor(&statsd.NoOpClient{}, "env", "service", "v1", &url.URL{Scheme: "http", Host: "agent-address"}, client) p.Start() for i := 0; i < b.N; i++ { p.SetCheckpointWithParams(context.Background(), options.CheckpointParams{PayloadSize: 1000}, "type:edge-1", "direction:in", "type:kafka", "topic:topic1", "group:group1") diff --git a/internal/exectracetest/exectrace_test.go b/internal/exectracetest/exectrace_test.go index ffeb280d66..6587397bce 100644 --- a/internal/exectracetest/exectrace_test.go +++ b/internal/exectracetest/exectrace_test.go @@ -16,10 +16,13 @@ package exectracetest import ( "bytes" "context" + "encoding/binary" "io" + "reflect" "regexp" "runtime/pprof" "runtime/trace" + "sort" "testing" "time" @@ -139,3 +142,60 @@ func TestSpanDoubleFinish(t *testing.T) { } // TODO: move database/sql tests here? likely requires copying over contrib/sql/internal.MockDriver + +func TestExecutionTraceSpans(t *testing.T) { + var root, child tracer.Span + _, execTrace := collectTestData(t, func() { + tracer.Start(tracer.WithLogger(discardLogger{})) + defer tracer.Stop() + var ctx context.Context + root, ctx = tracer.StartSpanFromContext(context.Background(), "root") + child, _ = tracer.StartSpanFromContext(ctx, "child") + root.Finish() + child.Finish() + }) + + type traceSpan struct { + name string + parent string + spanID uint64 + } + + spans := make(map[exptrace.TaskID]*traceSpan) + for _, ev := range execTrace { + switch ev.Kind() { + case exptrace.EventTaskBegin: + task := ev.Task() + var parent string + if p, ok := spans[task.Parent]; ok { + parent = p.name + } + spans[task.ID] = &traceSpan{ + name: task.Type, + parent: parent, + } + case exptrace.EventLog: + span, ok := spans[ev.Log().Task] + if !ok { + continue + } + if key := ev.Log().Category; key == "datadog.uint64_span_id" { + span.spanID = binary.LittleEndian.Uint64([]byte(ev.Log().Message)) + } + } + } + + want := []traceSpan{ + {name: "child", parent: "root", spanID: child.Context().SpanID()}, + {name: "root", spanID: root.Context().SpanID()}, + } + var got []traceSpan + for _, v := range spans { + got = append(got, *v) + } + sort.Slice(got, func(i, j int) bool { return got[i].name < got[j].name }) + + if !reflect.DeepEqual(want, got) { + t.Errorf("wanted spans %+v, got %+v", want, got) + } +} diff --git a/internal/exectracetest/go.mod b/internal/exectracetest/go.mod index 1988a4cbba..1b26b20b61 100644 --- a/internal/exectracetest/go.mod +++ b/internal/exectracetest/go.mod @@ -1,6 +1,8 @@ module gopkg.in/DataDog/dd-trace-go.v1/internal/exectracetest -go 1.20 +go 1.21 + +toolchain go1.21.0 require ( github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b @@ -32,7 +34,7 @@ require ( golang.org/x/sys v0.20.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.21.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/internal/exectracetest/go.sum b/internal/exectracetest/go.sum index 309906e02f..1a1bc2e820 100644 --- a/internal/exectracetest/go.sum +++ b/internal/exectracetest/go.sum @@ -11,6 +11,7 @@ github.com/DataDog/go-libddwaf/v3 v3.2.1/go.mod h1:AP+7Atb8ftSsrha35wht7+K3R+xuz github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I= github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= +github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= github.com/DataDog/sketches-go v1.4.5 h1:ki7VfeNz7IcNafq7yI/j5U/YCkO3LJiMDtXz9OMQbyE= github.com/DataDog/sketches-go v1.4.5/go.mod h1:7Y8GN8Jf66DLyDhc94zuWA3uHEt/7ttt8jHOBWWrSOg= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= @@ -22,6 +23,7 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -29,22 +31,36 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 h1:8EXxF+tCLqaVk8AOC29zl2mnhQjwyLxxOTuhUazWRsg= +github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4/go.mod h1:I5sHm0Y0T1u5YjlyqC5GVArM7aNZRUYtTjmJ8mPJFds= github.com/ebitengine/purego v0.6.0-alpha.5 h1:EYID3JOAdmQ4SNZYJHu9V6IqOeRQDBYxqKAg9PyoHFY= github.com/ebitengine/purego v0.6.0-alpha.5/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= @@ -53,23 +69,31 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= +github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg= github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -78,10 +102,12 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -134,13 +160,21 @@ golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/gotraceui v0.2.0 h1:dmNsfQ9Vl3GwbiVD7Z8d/osC6WtGGrasyrC2suc4ZIQ= +modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= +modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= diff --git a/internal/orchestrion/context.go b/internal/orchestrion/context.go new file mode 100644 index 0000000000..f66d12c576 --- /dev/null +++ b/internal/orchestrion/context.go @@ -0,0 +1,72 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package orchestrion + +import ( + "context" +) + +// WrapContext returns the GLS-wrapped context if orchestrion is enabled, otherwise it returns the given parameter. +func WrapContext(ctx context.Context) context.Context { + if !Enabled() { + return ctx + } + + if ctx != nil { + if _, ok := ctx.(*glsContext); ok { // avoid (some) double wrapping + return ctx + } + } + + if ctx == nil { + ctx = context.Background() + } + + return &glsContext{ctx} +} + +// CtxWithValue runs context.WithValue, adds the result to the GLS slot of orchestrion, and returns it. +// If orchestrion is not enabled, it will run context.WithValue and return the result. +// Since we don't support cross-goroutine switch of the GLS we still run context.WithValue in the case +// we are switching goroutines. +func CtxWithValue(parent context.Context, key, val any) context.Context { + if !Enabled() { + return context.WithValue(parent, key, val) + } + + getDDContextStack().Push(key, val) + return context.WithValue(WrapContext(parent), key, val) +} + +// GLSPopValue pops the value from the GLS slot of orchestrion and returns it. Using context.Context values usually does +// not require to pop any stack because the copy of each previous context makes the local variable in the scope disappear +// when the current function ends. But the GLS is a semi-global variable that can be accessed from any function in the +// stack, so we need to pop the value when we are done with it. +func GLSPopValue(key any) any { + if !Enabled() { + return nil + } + + return getDDContextStack().Pop(key) +} + +var _ context.Context = (*glsContext)(nil) + +type glsContext struct { + context.Context +} + +func (g *glsContext) Value(key any) any { + if !Enabled() { + return g.Context.Value(key) + } + + if val := getDDContextStack().Peek(key); val != nil { + return val + } + + return g.Context.Value(key) +} diff --git a/internal/orchestrion/context_stack.go b/internal/orchestrion/context_stack.go new file mode 100644 index 0000000000..6b4e408acc --- /dev/null +++ b/internal/orchestrion/context_stack.go @@ -0,0 +1,62 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package orchestrion + +// contextStack is the object put in the GLS slot of runtime.g inserted by orchestrion. it is used to store context values +// that are shared across the same goroutine. +// TODO: handle cross-goroutine context values +type contextStack map[any][]any + +// getDDContextStack is a main way to access the GLS slot of runtime.g inserted by orchestrion. This function should not be +// called if the enabled variable is false. +func getDDContextStack() *contextStack { + if gls := getDDGLS(); gls != nil { + return gls.(*contextStack) + } + + newStack := new(contextStack) + setDDGLS(newStack) + return newStack +} + +// Peek returns the top context from the stack without removing it. +func (s *contextStack) Peek(key any) any { + if s == nil { + return nil + } + + stack, ok := (*s)[key] + if !ok || len(stack) == 0 { + return nil + } + + return (*s)[key][len(stack)-1] +} + +// Push adds a context to the stack. +func (s *contextStack) Push(key, val any) { + if s == nil { + return + } + + (*s)[key] = append((*s)[key], val) +} + +// Pop removes the top context from the stack and returns it. +func (s *contextStack) Pop(key any) any { + if s == nil { + return nil + } + + stack, ok := (*s)[key] + if !ok || len(stack) == 0 { + return nil + } + + val := (*s)[key][len(stack)-1] + (*s)[key] = (*s)[key][:len(stack)-1] + return val +} diff --git a/internal/orchestrion/context_test.go b/internal/orchestrion/context_test.go new file mode 100644 index 0000000000..9bbf48cd7b --- /dev/null +++ b/internal/orchestrion/context_test.go @@ -0,0 +1,89 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package orchestrion + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +type key string + +func mockGLSGetterAndSetter() func() { + prevGetDDGLS := getDDGLS + prevSetDDGLS := setDDGLS + prevEnabled := enabled + + tmp := contextStack(make(map[any][]any)) + var glsValue any = &tmp + getDDGLS = func() any { return glsValue } + setDDGLS = func(a any) { + glsValue = a + } + + return func() { + getDDGLS = prevGetDDGLS + setDDGLS = prevSetDDGLS + enabled = prevEnabled + } +} + +func TestFromGLS(t *testing.T) { + cleanup := mockGLSGetterAndSetter() + defer cleanup() + + t.Run("Enabled() is false, ctx is nil", func(t *testing.T) { + enabled = false + require.Equal(t, nil, WrapContext(nil)) + }) + + t.Run("Enabled() is false, ctx is not nil", func(t *testing.T) { + enabled = false + require.Equal(t, context.Background(), WrapContext(context.Background())) + + }) + + t.Run("Enabled() is true, ctx is nil", func(t *testing.T) { + enabled = true + require.Equal(t, &glsContext{context.Background()}, WrapContext(nil)) + }) + + t.Run("Enabled() is true, ctx is not nil", func(t *testing.T) { + enabled = true + ctx := context.WithValue(context.Background(), key("key"), "value") + require.Equal(t, &glsContext{ctx}, WrapContext(ctx)) + }) +} + +func TestCtxWithValue(t *testing.T) { + cleanup := mockGLSGetterAndSetter() + defer cleanup() + + t.Run("false", func(t *testing.T) { + enabled = false + require.Equal(t, context.WithValue(context.Background(), key("key"), "value"), CtxWithValue(context.Background(), key("key"), "value")) + }) + + t.Run("true", func(t *testing.T) { + enabled = true + ctx := CtxWithValue(context.Background(), key("key"), "value") + require.Equal(t, context.WithValue(&glsContext{context.Background()}, key("key"), "value"), ctx) + require.Equal(t, "value", ctx.Value(key("key"))) + require.Equal(t, "value", getDDContextStack().Peek(key("key"))) + require.Equal(t, "value", GLSPopValue(key("key"))) + require.Nil(t, getDDContextStack().Peek(key("key"))) + }) + + t.Run("cross-goroutine switch", func(t *testing.T) { + enabled = true + ctx := CtxWithValue(context.Background(), key("key"), "value") + go func() { + require.Equal(t, "value", ctx.Value(key("key"))) + }() + }) +} diff --git a/internal/orchestrion/gls.go b/internal/orchestrion/gls.go new file mode 100644 index 0000000000..d117759c05 --- /dev/null +++ b/internal/orchestrion/gls.go @@ -0,0 +1,41 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package orchestrion + +import ( + _ "runtime" // to make sure the symbols we link to are present + _ "unsafe" // for go:linkname +) + +var ( + // getDDGLS returns the current value from the field inserted in runtime.g by orchestrion. + // Or nil if orchestrion is not enabled. + getDDGLS = func() any { return nil } + // setDDGLS sets the value in the field inserted in runtime.g by orchestrion. + // Or does nothing if orchestrion is not enabled. + setDDGLS = func(any) {} +) + +// Accessors set by orchestrion in the runtime package. If orchestrion is not enabled, these will be nil as per the default values. + +//revive:disable:var-naming +//go:linkname __dd_orchestrion_gls_get __dd_orchestrion_gls_get +var __dd_orchestrion_gls_get func() any + +//go:linkname __dd_orchestrion_gls_set __dd_orchestrion_gls_set +var __dd_orchestrion_gls_set func(any) + +//revive:enable:var-naming + +// Check at Go init time that the two function variable values created by the +// orchestrion are present, and set the get/set variables to their +// values. +func init() { + if __dd_orchestrion_gls_get != nil && __dd_orchestrion_gls_set != nil { + getDDGLS = __dd_orchestrion_gls_get + setDDGLS = __dd_orchestrion_gls_set + } +} diff --git a/internal/orchestrion/orchestrion.go b/internal/orchestrion/orchestrion.go new file mode 100644 index 0000000000..679593652e --- /dev/null +++ b/internal/orchestrion/orchestrion.go @@ -0,0 +1,16 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package orchestrion + +// Orchestrion will change this at build-time +// +//dd:orchestrion-enabled +var enabled = false + +// Enabled returns whether the current build was compiled with orchestrion or not. +func Enabled() bool { + return enabled +} diff --git a/internal/telemetry/client.go b/internal/telemetry/client.go index 1b01ced31f..5cf962c7e4 100644 --- a/internal/telemetry/client.go +++ b/internal/telemetry/client.go @@ -211,6 +211,18 @@ func (c *client) start(configuration []Configuration, namespace Namespace, flush cfg = append(cfg, c.globalAppConfig...) cfg = append(cfg, configuration...) + // State whether the app has its Go dependencies available or not + deps, ok := debug.ReadBuildInfo() + if !ok { + deps = nil // because not guaranteed to be nil by the public doc when !ok + } + cfg = append(cfg, BoolConfig("dependencies_available", ok)) + collectDependenciesEnabled := collectDependencies() + cfg = append(cfg, BoolConfig("DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED", collectDependenciesEnabled)) // TODO: report all the possible telemetry config option automatically + if !collectDependenciesEnabled { + deps = nil // to simplify the condition below to `deps != nil` + } + payload := &AppStarted{ Configuration: cfg, Products: productInfo, @@ -219,18 +231,19 @@ func (c *client) start(configuration []Configuration, namespace Namespace, flush appStarted.Body.Payload = payload c.scheduleSubmit(appStarted) - if collectDependencies() { + if deps != nil { var depPayload Dependencies - if deps, ok := debug.ReadBuildInfo(); ok { - for _, dep := range deps.Deps { - depPayload.Dependencies = append(depPayload.Dependencies, - Dependency{ - Name: dep.Path, - Version: strings.TrimPrefix(dep.Version, "v"), - }, - ) - } + for _, dep := range deps.Deps { + depPayload.Dependencies = append(depPayload.Dependencies, + Dependency{ + Name: dep.Path, + Version: strings.TrimPrefix(dep.Version, "v"), + }, + ) } + // Send the telemetry request if and only if the dependencies are actually present in the binary. + // For instance, bazel doesn't include them out of the box (cf. https://github.com/bazelbuild/rules_go/issues/3090), + // which would result in an empty list of dependencies. dep := c.newRequest(RequestTypeDependenciesLoaded) dep.Body.Payload = depPayload c.scheduleSubmit(dep) diff --git a/internal/telemetry/telemetry_test.go b/internal/telemetry/telemetry_test.go index 2666a91c44..cad9cd43ea 100644 --- a/internal/telemetry/telemetry_test.go +++ b/internal/telemetry/telemetry_test.go @@ -151,7 +151,6 @@ func TestRegisterAppConfig(t *testing.T) { require.Equal(t, RequestTypeAppStarted, req.RequestType) appStarted := req.Payload.(*AppStarted) cfg := appStarted.Configuration - require.Len(t, cfg, 2) require.Contains(t, cfg, Configuration{Name: "key1", Value: "val1", Origin: OriginDefault}) require.Contains(t, cfg, Configuration{Name: "key2", Value: "val2", Origin: OriginDDConfig}) diff --git a/internal/trace_context.go b/internal/trace_context.go index 47401fb6fe..78620b6929 100644 --- a/internal/trace_context.go +++ b/internal/trace_context.go @@ -7,6 +7,8 @@ package internal import ( "context" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/orchestrion" ) type executionTracedKey struct{} @@ -23,7 +25,7 @@ type executionTracedKey struct{} // general, APM instrumentation should prefer creating tasks around the // operation rather than after the fact, if possible. func WithExecutionTraced(ctx context.Context) context.Context { - return context.WithValue(ctx, executionTracedKey{}, true) + return orchestrion.CtxWithValue(ctx, executionTracedKey{}, true) } // WithExecutionNotTraced marks that the context is *not* covered by an @@ -31,17 +33,17 @@ func WithExecutionTraced(ctx context.Context) context.Context { // information from ctx) from being considered covered by a task, when an // integration may create its own child span with its own execution trace task. func WithExecutionNotTraced(ctx context.Context) context.Context { - if ctx.Value(executionTracedKey{}) == nil { + if orchestrion.WrapContext(ctx).Value(executionTracedKey{}) == nil { // Fast path: if it wasn't marked before, we don't need to wrap // the context return ctx } - return context.WithValue(ctx, executionTracedKey{}, false) + return orchestrion.CtxWithValue(ctx, executionTracedKey{}, false) } // IsExecutionTraced returns whether ctx is associated with an execution trace // task, as indicated via WithExecutionTraced func IsExecutionTraced(ctx context.Context) bool { - v := ctx.Value(executionTracedKey{}) + v := orchestrion.WrapContext(ctx).Value(executionTracedKey{}) return v != nil && v.(bool) } diff --git a/static-analysis.datadog.yml b/static-analysis.datadog.yml new file mode 100644 index 0000000000..bb08fa3286 --- /dev/null +++ b/static-analysis.datadog.yml @@ -0,0 +1,5 @@ +rulesets: + - sit-ci-best-practices: + only: + - ".github/workflows" +