Skip to content

Add a resource detector for Gitlab CICD #6760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add support for configuring `Insecure` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6658)
- Support for the `OTEL_HTTP_CLIENT_COMPATIBILITY_MODE=http/dup` environment variable in `instrumentation/net/http/httptrace/otelhttptrace` to emit attributes for both the v1.20.0 and v1.26.0 semantic conventions. (#6720)
- Support for the `OTEL_HTTP_CLIENT_COMPATIBILITY_MODE=http/dup` environment variable in `instrumentation/github.com/emicklei/go-restful/otelrestful` to emit attributes for both the v1.20.0 and v1.26.0 semantic conventions. (#6710)
- Add the new `go.opentelemetry.io/contrib/detectors/cicd/gitlab` package to provide a resource detector for Gitlab CI. (#6760)

### Changed

Expand Down
26 changes: 26 additions & 0 deletions detectors/cicd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# CI/CD Resource Detectors

## Gitlab

Sample code snippet to initialize Gitlab resource detector

```
// Instantiate a new Gitlab CICD Resource detector
gitlabResourceDetector := gitlab.NewResourceDetector()
resource, err := gitlabResourceDetector.Detect(context.Background())
```

Gitlab CI/CD resource detector captures following Gitlab Job environment attributes

```
cicd.pipeline.name
cicd.pipeline.task.run.id
cicd.pipeline.task.name
cicd.pipeline.task.type
cicd.pipeline.run.id
cicd.pipeline.task.run.url.full
vcs.repository.ref.name
vcs.repository.ref.type
vcs.repository.change.id
vcs.repository.url.full
```
79 changes: 79 additions & 0 deletions detectors/cicd/gitlab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# OpenTelemetry Gitlab CI/CD Detector for Golang

[![Go Reference][goref-image]][goref-url]
[![Apache License][license-image]][license-url]

This module detects resource attributes available in Gitlab CI Pipeline.

## Installation

```bash
go get -u go.opentelemetry.io/contrib/detectors/cicd/gitlab
```

## Usage

Create a sample Go application such as below.

```go
package main

import (
sdktrace "go.opencensus.io/otel/sdk/trace"
gitlabdetector "go.opentelemetry.io/contrib/detectors/cicd/gitlab"
)

func main() {
detector := gitlabdetector.NewResourceDetector()
res, err := detector.Detect(context.Background())
if err != nil {
fmt.Printf("failed to detect gitlab CICD resources: %v\n", err)
}

tp := sdktrace.NewTracerProvider(
sdktrace.WithResource(res),
)

...
}
```

Now your `TracerProvider` will have the following resource attributes and attach them to new spans:

| Resource Attribute | Example Value |
|---------------------------------|--------------------------------|
| cicd.pipeline.name | test |
| cicd.pipeline.task.run.id | 123 |
| cicd.pipeline.task.name | unit-test |
| cicd.pipeline.task.type | test |
| cicd.pipeline.run.id | 12345 |
| cicd.pipeline.task.run.url.full | https://gitlab/job/123 |
| vcs.repository.ref.name | myProject |
| vcs.repository.ref.type | branch |
| vcs.repository.change.id | 12 |
| vcs.repository.url.full | https://gitlab/myOrg/myProject |

## Useful links

- For more on CI/CD Gitlab Predefined variables,
visit <https://docs.gitlab.com/ee/ci/variables/predefined_variables.html#predefined-variables>
- For more on CI/CD pipeline attribute conventions,
visit <https://opentelemetry.io/docs/specs/semconv/attributes-registry/cicd/>
- For more on VCS attribute conventions, visit <https://opentelemetry.io/docs/specs/semconv/attributes-registry/vcs/>
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
- For more about OpenTelemetry Go: <https://github.com/open-telemetry/opentelemetry-go>
- For help or feedback on this project, join us in [GitHub Discussions][discussions-url]

## License

Apache 2.0 - See [LICENSE][license-url] for more information.

[license-url]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/LICENSE

[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat

[goref-image]: https://pkg.go.dev/badge/go.opentelemetry.io/contrib/detectors/cicd/gitlab.svg

[goref-url]: https://pkg.go.dev/go.opentelemetry.io/contrib/detectors/cicd/gitlab

[discussions-url]: https://github.com/open-telemetry/opentelemetry-go/discussions
25 changes: 25 additions & 0 deletions detectors/cicd/gitlab/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

/*
Package gitlab provides a [resource.Detector] which supports detecting
attributes specific to Gitlab CI.

According to semantic conventions for [cicd] and [vcs] attributes,
each of the following attributes is added if it is available:

- cicd.pipeline.name
- cicd.pipeline.task.run.id
- cicd.pipeline.task.name
- cicd.pipeline.task.type
- cicd.pipeline.run.id
- cicd.pipeline.task.run.url.full
- vcs.repository.ref.name
- vcs.repository.ref.type
- vcs.repository.change.id
- vcs.repository.url.full

[cicd]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/cicd.md
[vcs]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/vcs.md
*/
package gitlab // import "go.opentelemetry.io/contrib/detectors/cicd/gitlab"
122 changes: 122 additions & 0 deletions detectors/cicd/gitlab/gitlab.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package gitlab

import (
"context"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
"os"
)

const (
gitlabCIEnvVar = "GITLAB_CI"
gitlabPipelineNameEnvVar = "CI_PIPELINE_NAME"
gitlabPipelineIdEnvVar = "CI_PIPELINE_ID"
gitlabJobIdEnvVar = "CI_JOB_ID"
gitlabJobNameEnvVar = "CI_JOB_NAME"
gitlabJobStageEnvVar = "CI_JOB_STAGE"
gitlabJobUrlEnvVar = "CI_JOB_URL"

gitlabCommitRefNameEnvVar = "CI_COMMIT_REF_NAME"
gitlabCommitTagEnvVar = "CI_COMMIT_TAG"
gitlabMergeRequestIIDEnvVar = "CI_MERGE_REQUEST_IID"

gitlabProjectUrlEnvVar = "CI_PROJECT_URL"
gitlabProjectIDEnvVar = "CI_PROJECT_ID"
)

type resourceDetector struct {
}

// compile time assertion that resourceDetector implements the resource.Detector interface.
var _ resource.Detector = (*resourceDetector)(nil)

// NewResourceDetector returns a [ResourceDetector] that will detect Gitlab Pipeline resources.
func NewResourceDetector() resource.Detector {
return &resourceDetector{}
}

func (detector *resourceDetector) Detect(_ context.Context) (*resource.Resource, error) {

var attributes []attribute.KeyValue

isGitlabCI := os.Getenv(gitlabCIEnvVar) == "true"

if isGitlabCI {
attributes = append(attributes, detectCICDAttributes()...)
attributes = append(attributes, detectVCSAttributes()...)
}

return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil
}

// detectCICDAttributes https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/cicd.md
func detectCICDAttributes() []attribute.KeyValue {
var attributes []attribute.KeyValue

ciPipelineName := os.Getenv(gitlabPipelineNameEnvVar)
if ciPipelineName != "" {
attributes = append(attributes, semconv.CICDPipelineNameKey.String(ciPipelineName))
}

ciJobId := os.Getenv(gitlabJobIdEnvVar)
if ciJobId != "" {
attributes = append(attributes, semconv.CICDPipelineTaskRunIDKey.String(ciJobId))
}

ciJobName := os.Getenv(gitlabJobNameEnvVar)
if ciJobName != "" {
attributes = append(attributes, semconv.CICDPipelineTaskNameKey.String(ciJobName))
}

ciJobStage := os.Getenv(gitlabJobStageEnvVar)
if ciJobStage != "" {
attributes = append(attributes, semconv.CICDPipelineTaskTypeKey.String(ciJobStage))
}

ciPipelineId := os.Getenv(gitlabPipelineIdEnvVar)
if ciPipelineId != "" {
attributes = append(attributes, semconv.CICDPipelineRunIDKey.String(ciPipelineId))
}

ciPipelineUrl := os.Getenv(gitlabJobUrlEnvVar)
if ciPipelineUrl != "" {
attributes = append(attributes, semconv.CICDPipelineTaskRunURLFullKey.String(ciPipelineUrl))
}
return attributes
}

// detectVCSAttributes https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/vcs.md
func detectVCSAttributes() []attribute.KeyValue {
var attributes []attribute.KeyValue

ciRefName := os.Getenv(gitlabCommitRefNameEnvVar)
if ciRefName != "" {
attributes = append(attributes, semconv.VCSRepositoryRefNameKey.String(ciRefName))
}

ciTag := os.Getenv(gitlabCommitTagEnvVar)
if ciTag != "" {
attributes = append(attributes, semconv.VCSRepositoryRefTypeTag)
} else {
attributes = append(attributes, semconv.VCSRepositoryRefTypeBranch)
}

mrID := os.Getenv(gitlabMergeRequestIIDEnvVar)
if mrID != "" {
attributes = append(attributes, semconv.VCSRepositoryChangeIDKey.String(mrID))
}

projectUrl := os.Getenv(gitlabProjectUrlEnvVar)
if projectUrl != "" {
attributes = append(attributes, semconv.VCSRepositoryURLFullKey.String(projectUrl))
}

// There is no SemConv for the ProjectID var
//projectID := os.Getenv(gitlabProjectIDEnvVar)
//if projectID != "" {
// attributes = append(attributes, semconv.VCSRepositoryProjectID.String(projectID))
//}

return attributes
}
76 changes: 76 additions & 0 deletions detectors/cicd/gitlab/gitlab_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package gitlab

import (
"context"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
"testing"
)

type envPair struct {
Key string
Value string
}

func setTestEnv(t *testing.T, envs []envPair) {
for _, env := range envs {
t.Setenv(env.Key, env.Value)
}
}

func TestGitlabDetector(t *testing.T) {

tcs := []struct {
scenario string
envs []envPair
expectedError error
expectedResource *resource.Resource
}{
{
scenario: "all env configured",
envs: []envPair{
{"CI", "true"},
{"GITLAB_CI", "true"},
{"CI_PIPELINE_NAME", "pipeline_name"},
{"CI_JOB_ID", "123"},
{"CI_JOB_NAME", "test something"},
{"CI_JOB_STAGE", "test"},
{"CI_PIPELINE_ID", "12345"},
{"CI_JOB_URL", "https://gitlab/job/123"},
{"CI_COMMIT_REF_NAME", "abc123"},
{"CI_MERGE_REQUEST_IID", "12"},
{"CI_PROJECT_URL", "https://gitlab/org/project"},
{"CI_PROJECT_ID", "111"},
},
expectedError: nil,
expectedResource: resource.NewWithAttributes(semconv.SchemaURL, []attribute.KeyValue{
attribute.String(string(semconv.CICDPipelineNameKey), "pipeline_name"),
attribute.String(string(semconv.CICDPipelineTaskRunIDKey), "123"),
attribute.String(string(semconv.CICDPipelineTaskNameKey), "test something"),
attribute.String(string(semconv.CICDPipelineTaskTypeKey), "test"),
attribute.String(string(semconv.CICDPipelineRunIDKey), "12345"),
attribute.String(string(semconv.CICDPipelineTaskRunURLFullKey), "https://gitlab/job/123"),
attribute.String(string(semconv.VCSRepositoryRefNameKey), "abc123"),
attribute.String(string(semconv.VCSRepositoryRefTypeKey), "branch"),
attribute.String(string(semconv.VCSRepositoryChangeIDKey), "12"),
attribute.String(string(semconv.VCSRepositoryURLFullKey), "https://gitlab/org/project"),
// attribute.String(string(semconv.VCSRepositoryProjectID), "111"),
}...),
},
}
for _, tc := range tcs {
t.Run(tc.scenario, func(t *testing.T) {
setTestEnv(t, tc.envs)

detector := NewResourceDetector()

res, err := detector.Detect(context.Background())

assert.Equal(t, tc.expectedError, err)
assert.Equal(t, tc.expectedResource, res)
})
}

}
22 changes: 22 additions & 0 deletions detectors/cicd/gitlab/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module go.opentelemetry.io/contrib/detectors/cicd/gitlab

go 1.22.0

require (
github.com/stretchr/testify v1.10.0
go.opentelemetry.io/otel v1.34.0
go.opentelemetry.io/otel/sdk v1.34.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
golang.org/x/sys v0.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
30 changes: 30 additions & 0 deletions detectors/cicd/gitlab/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading