Skip to content

Commit abec055

Browse files
chameleon82Aleksandr Nekrasov
authored and
Aleksandr Nekrasov
committed
Add a resource detector for Gitlab CICD
1 parent daafaee commit abec055

File tree

8 files changed

+384
-0
lines changed

8 files changed

+384
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1919
- Add support for configuring `Insecure` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6658)
2020
- 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)
2121
- 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)
22+
- Add the new `go.opentelemetry.io/contrib/detectors/cicd/gitlab` package to provide a resource detector for Gitlab CI. (6760)
2223

2324
### Changed
2425

detectors/cicd/README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# CI/CD Resource Detectors
2+
3+
## Gitlab
4+
5+
Sample code snippet to initialize Gitlab resource detector
6+
7+
```
8+
// Instantiate a new Gitlab CICD Resource detector
9+
gitlabResourceDetector := gitlab.NewResourceDetector()
10+
resource, err := gitlabResourceDetector.Detect(context.Background())
11+
```
12+
13+
Gitlab CI/CD resource detector captures following Gitlab Job environment attributes
14+
15+
```
16+
cicd.pipeline.name
17+
cicd.pipeline.task.run.id
18+
cicd.pipeline.task.name
19+
cicd.pipeline.task.type
20+
cicd.pipeline.run.id
21+
cicd.pipeline.task.run.url.full
22+
vcs.repository.ref.name
23+
vcs.repository.ref.type
24+
vcs.repository.change.id
25+
vcs.repository.url.full
26+
```

detectors/cicd/gitlab/README.md

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# OpenTelemetry Gitlab CI/CD Detector for Golang
2+
3+
[![Go Reference][goref-image]][goref-url]
4+
[![Apache License][license-image]][license-url]
5+
6+
This module detects resource attributes available in Gitlab CI Pipeline.
7+
8+
## Installation
9+
10+
```bash
11+
go get -u go.opentelemetry.io/contrib/detectors/cicd/gitlab
12+
```
13+
14+
## Usage
15+
16+
Create a sample Go application such as below.
17+
18+
```go
19+
package main
20+
21+
import (
22+
sdktrace "go.opencensus.io/otel/sdk/trace"
23+
gitlabdetector "go.opentelemetry.io/contrib/detectors/cicd/gitlab"
24+
)
25+
26+
func main() {
27+
detector := gitlabdetector.NewResourceDetector()
28+
res, err := detector.Detect(context.Background())
29+
if err != nil {
30+
fmt.Printf("failed to detect gitlab CICD resources: %v\n", err)
31+
}
32+
33+
tp := sdktrace.NewTracerProvider(
34+
sdktrace.WithResource(res),
35+
)
36+
37+
...
38+
}
39+
```
40+
41+
Now your `TracerProvider` will have the following resource attributes and attach them to new spans:
42+
43+
| Resource Attribute | Example Value |
44+
|---------------------------------|--------------------------------|
45+
| cicd.pipeline.name | test |
46+
| cicd.pipeline.task.run.id | 123 |
47+
| cicd.pipeline.task.name | unit-test |
48+
| cicd.pipeline.task.type | test |
49+
| cicd.pipeline.run.id | 12345 |
50+
| cicd.pipeline.task.run.url.full | https://gitlab/job/123 |
51+
| vcs.repository.ref.name | myProject |
52+
| vcs.repository.ref.type | branch |
53+
| vcs.repository.change.id | 12 |
54+
| vcs.repository.url.full | https://gitlab/myOrg/myProject |
55+
56+
## Useful links
57+
58+
- For more on CI/CD pipeline attribute conventions,
59+
visit <https://opentelemetry.io/docs/specs/semconv/attributes-registry/cicd/>
60+
- For more on VCS attribute conventions, visit <https://opentelemetry.io/docs/specs/semconv/attributes-registry/vcs/>
61+
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
62+
- For more about OpenTelemetry Go: <https://github.com/open-telemetry/opentelemetry-go>
63+
- For help or feedback on this project, join us in [GitHub Discussions][discussions-url]
64+
65+
## License
66+
67+
Apache 2.0 - See [LICENSE][license-url] for more information.
68+
69+
[license-url]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/LICENSE
70+
71+
[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat
72+
73+
[goref-image]: https://pkg.go.dev/badge/go.opentelemetry.io/contrib/detectors/cicd/gitlab.svg
74+
75+
[goref-url]: https://pkg.go.dev/go.opentelemetry.io/contrib/detectors/cicd/gitlab
76+
77+
[discussions-url]: https://github.com/open-telemetry/opentelemetry-go/discussions

detectors/cicd/gitlab/doc.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/*
5+
Package gitlab provides a [resource.Detector] which supports detecting
6+
attributes specific to Gitlab CI.
7+
8+
According to semantic conventions for [cicd] and [vcs] attributes,
9+
each of the following attributes is added if it is available:
10+
11+
- cicd.pipeline.name
12+
- cicd.pipeline.task.run.id
13+
- cicd.pipeline.task.name
14+
- cicd.pipeline.task.type
15+
- cicd.pipeline.run.id
16+
- cicd.pipeline.task.run.url.full
17+
- vcs.repository.ref.name
18+
- vcs.repository.ref.type
19+
- vcs.repository.change.id
20+
- vcs.repository.url.full
21+
22+
[cicd]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/cicd.md
23+
[vcs]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/vcs.md
24+
*/
25+
package gitlab // import "go.opentelemetry.io/contrib/detectors/cicd/gitlab"

detectors/cicd/gitlab/gitlab.go

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package gitlab
2+
3+
import (
4+
"context"
5+
"go.opentelemetry.io/otel/attribute"
6+
"go.opentelemetry.io/otel/sdk/resource"
7+
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
8+
"os"
9+
)
10+
11+
const (
12+
gitlabCIEnvVar = "GITLAB_CI"
13+
gitlabPipelineNameEnvVar = "CI_PIPELINE_NAME"
14+
gitlabPipelineIdEnvVar = "CI_PIPELINE_ID"
15+
gitlabJobIdEnvVar = "CI_JOB_ID"
16+
gitlabJobNameEnvVar = "CI_JOB_NAME"
17+
gitlabJobStageEnvVar = "CI_JOB_STAGE"
18+
gitlabJobUrlEnvVar = "CI_JOB_URL"
19+
20+
gitlabCommitRefNameEnvVar = "CI_COMMIT_REF_NAME"
21+
gitlabCommitTagEnvVar = "CI_COMMIT_TAG"
22+
gitlabMergeRequestIIDEnvVar = "CI_MERGE_REQUEST_IID"
23+
24+
gitlabProjectUrlEnvVar = "CI_PROJECT_URL"
25+
gitlabProjectIDEnvVar = "CI_PROJECT_ID"
26+
)
27+
28+
type resourceDetector struct {
29+
}
30+
31+
// compile time assertion that resourceDetector implements the resource.Detector interface.
32+
var _ resource.Detector = (*resourceDetector)(nil)
33+
34+
// NewResourceDetector returns a [ResourceDetector] that will detect Gitlab Pipeline resources.
35+
func NewResourceDetector() resource.Detector {
36+
return &resourceDetector{}
37+
}
38+
39+
func (detector *resourceDetector) Detect(_ context.Context) (*resource.Resource, error) {
40+
41+
var attributes []attribute.KeyValue
42+
43+
isGitlabCI := os.Getenv(gitlabCIEnvVar) == "true"
44+
45+
if isGitlabCI {
46+
attributes = append(attributes, detectCICDAttributes()...)
47+
attributes = append(attributes, detectVCSAttributes()...)
48+
}
49+
50+
return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil
51+
}
52+
53+
// detectCICDAttributes https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/cicd.md
54+
func detectCICDAttributes() []attribute.KeyValue {
55+
var attributes []attribute.KeyValue
56+
57+
ciPipelineName := os.Getenv(gitlabPipelineNameEnvVar)
58+
if ciPipelineName != "" {
59+
attributes = append(attributes, attribute.String(string(semconv.CICDPipelineNameKey), ciPipelineName))
60+
}
61+
62+
ciJobId := os.Getenv(gitlabJobIdEnvVar)
63+
if ciJobId != "" {
64+
attributes = append(attributes, attribute.String(string(semconv.CICDPipelineTaskRunIDKey), ciJobId))
65+
}
66+
67+
ciJobName := os.Getenv(gitlabJobNameEnvVar)
68+
if ciJobName != "" {
69+
attributes = append(attributes, attribute.String(string(semconv.CICDPipelineTaskNameKey), ciJobName))
70+
}
71+
72+
ciJobStage := os.Getenv(gitlabJobStageEnvVar)
73+
if ciJobStage != "" {
74+
attributes = append(attributes, attribute.String(string(semconv.CICDPipelineTaskTypeKey), ciJobStage))
75+
}
76+
77+
ciPipelineId := os.Getenv(gitlabPipelineIdEnvVar)
78+
if ciPipelineId != "" {
79+
attributes = append(attributes, attribute.String(string(semconv.CICDPipelineRunIDKey), ciPipelineId))
80+
}
81+
82+
ciPipelineUrl := os.Getenv(gitlabJobUrlEnvVar)
83+
if ciPipelineUrl != "" {
84+
attributes = append(attributes, attribute.String(string(semconv.CICDPipelineTaskRunURLFullKey), ciPipelineUrl))
85+
}
86+
return attributes
87+
}
88+
89+
// detectVCSAttributes https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/vcs.md
90+
func detectVCSAttributes() []attribute.KeyValue {
91+
var attributes []attribute.KeyValue
92+
93+
ciRefName := os.Getenv(gitlabCommitRefNameEnvVar)
94+
if ciRefName != "" {
95+
attributes = append(attributes, attribute.String(string(semconv.VCSRepositoryRefNameKey), ciRefName))
96+
}
97+
98+
ciTag := os.Getenv(gitlabCommitTagEnvVar)
99+
if ciTag != "" {
100+
attributes = append(attributes, semconv.VCSRepositoryRefTypeTag)
101+
} else {
102+
attributes = append(attributes, semconv.VCSRepositoryRefTypeBranch)
103+
}
104+
105+
mrID := os.Getenv(gitlabMergeRequestIIDEnvVar)
106+
if mrID != "" {
107+
attributes = append(attributes, attribute.String(string(semconv.VCSRepositoryChangeIDKey), mrID))
108+
}
109+
110+
projectUrl := os.Getenv(gitlabProjectUrlEnvVar)
111+
if projectUrl != "" {
112+
attributes = append(attributes, attribute.String(string(semconv.VCSRepositoryURLFullKey), projectUrl))
113+
}
114+
115+
// There is no SemConv for the ProjectID var
116+
//projectID := os.Getenv(gitlabProjectIDEnvVar)
117+
//if projectID != "" {
118+
// attributes = append(attributes, attribute.String(string(semconv.VCSRepositoryProjectID), projectID))
119+
//}
120+
121+
return attributes
122+
}

detectors/cicd/gitlab/gitlab_test.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package gitlab
2+
3+
import (
4+
"context"
5+
"github.com/stretchr/testify/assert"
6+
"go.opentelemetry.io/otel/attribute"
7+
"go.opentelemetry.io/otel/sdk/resource"
8+
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
9+
"os"
10+
"testing"
11+
)
12+
13+
type EnvPair struct {
14+
Key string
15+
Value string
16+
}
17+
18+
func setTestEnv(t *testing.T, envs []EnvPair) {
19+
for _, env := range envs {
20+
err := os.Setenv(env.Key, env.Value)
21+
if err != nil {
22+
t.Fatalf("Failed to set environment variable %s: %v", env.Key, err)
23+
}
24+
}
25+
}
26+
27+
func TestGitlabDetector(t *testing.T) {
28+
29+
tcs := []struct {
30+
scenario string
31+
envs []EnvPair
32+
expectedError error
33+
expectedResource *resource.Resource
34+
}{
35+
{
36+
scenario: "all env configured",
37+
envs: []EnvPair{
38+
{"CI", "true"},
39+
{"GITLAB_CI", "true"},
40+
{"CI_PIPELINE_NAME", "pipeline_name"},
41+
{"CI_JOB_ID", "123"},
42+
{"CI_JOB_NAME", "test something"},
43+
{"CI_JOB_STAGE", "test"},
44+
{"CI_PIPELINE_ID", "12345"},
45+
{"CI_JOB_URL", "https://gitlab/job/123"},
46+
{"CI_COMMIT_REF_NAME", "abc123"},
47+
{"CI_MERGE_REQUEST_IID", "12"},
48+
{"CI_PROJECT_URL", "https://gitlab/org/project"},
49+
{"CI_PROJECT_ID", "111"},
50+
},
51+
expectedError: nil,
52+
expectedResource: resource.NewWithAttributes(semconv.SchemaURL, []attribute.KeyValue{
53+
attribute.String(string(semconv.CICDPipelineNameKey), "pipeline_name"),
54+
attribute.String(string(semconv.CICDPipelineTaskRunIDKey), "123"),
55+
attribute.String(string(semconv.CICDPipelineTaskNameKey), "test something"),
56+
attribute.String(string(semconv.CICDPipelineTaskTypeKey), "test"),
57+
attribute.String(string(semconv.CICDPipelineRunIDKey), "12345"),
58+
attribute.String(string(semconv.CICDPipelineTaskRunURLFullKey), "https://gitlab/job/123"),
59+
attribute.String(string(semconv.VCSRepositoryRefNameKey), "abc123"),
60+
attribute.String(string(semconv.VCSRepositoryRefTypeKey), "branch"),
61+
attribute.String(string(semconv.VCSRepositoryChangeIDKey), "12"),
62+
attribute.String(string(semconv.VCSRepositoryURLFullKey), "https://gitlab/org/project"),
63+
// attribute.String(string(semconv.VCSRepositoryProjectID), "111"),
64+
}...),
65+
},
66+
}
67+
for _, tc := range tcs {
68+
t.Run(tc.scenario, func(t *testing.T) {
69+
os.Clearenv()
70+
setTestEnv(t, tc.envs)
71+
72+
detector := NewResourceDetector()
73+
74+
res, err := detector.Detect(context.Background())
75+
76+
assert.Equal(t, tc.expectedError, err)
77+
assert.Equal(t, tc.expectedResource, res)
78+
})
79+
}
80+
81+
}

detectors/cicd/gitlab/go.mod

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module go.opentelemetry.io/contrib/detectors/cicd/gitlab
2+
3+
go 1.22.0
4+
5+
require (
6+
github.com/stretchr/testify v1.10.0
7+
go.opentelemetry.io/otel v1.34.0
8+
go.opentelemetry.io/otel/sdk v1.34.0
9+
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/go-logr/logr v1.4.2 // indirect
14+
github.com/go-logr/stdr v1.2.2 // indirect
15+
github.com/google/uuid v1.6.0 // indirect
16+
github.com/pmezard/go-difflib v1.0.0 // indirect
17+
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
18+
go.opentelemetry.io/otel/metric v1.34.0 // indirect
19+
go.opentelemetry.io/otel/trace v1.34.0 // indirect
20+
golang.org/x/sys v0.30.0 // indirect
21+
gopkg.in/yaml.v3 v3.0.1 // indirect
22+
)

detectors/cicd/gitlab/go.sum

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

0 commit comments

Comments
 (0)