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"
+