Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
```
77 changes: 77 additions & 0 deletions detectors/cicd/gitlab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# 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 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, attribute.String(string(semconv.CICDPipelineNameKey), ciPipelineName))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can adjust this kind of format. semconv.CICDPipelineNameKey.String("xxxxx")

}

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

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

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

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

ciPipelineUrl := os.Getenv(gitlabJobUrlEnvVar)
if ciPipelineUrl != "" {
attributes = append(attributes, attribute.String(string(semconv.CICDPipelineTaskRunURLFullKey), 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, attribute.String(string(semconv.VCSRepositoryRefNameKey), 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, attribute.String(string(semconv.VCSRepositoryChangeIDKey), mrID))
}

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

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

return attributes
}
81 changes: 81 additions & 0 deletions detectors/cicd/gitlab/gitlab_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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"
"os"
"testing"
)

type EnvPair struct {
Key string
Value string
}

func setTestEnv(t *testing.T, envs []EnvPair) {
for _, env := range envs {
err := os.Setenv(env.Key, env.Value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os.Setenv be changed to t.Setenv

if err != nil {
t.Fatalf("Failed to set environment variable %s: %v", env.Key, err)
}
}
}

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) {
os.Clearenv()
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=