Skip to content

Commit 778d8f7

Browse files
authored
[receiver/gitlab] add tracing via webhook skeleton (#36838)
#### Description This PR adds the structure and trace skeleton for a new and already accepted Gitlabreceiver. (thanks @atoulme for sponsoring this!) The Gitlabreceiver aligns very closely with the Githubreceiver and this PR mostly mirrors the change from this PR: #36632 I'm working together with @adrielp on building out the Gitlabreceiver. More PRs to introduce metrics and actual tracing functionality are about to follow with subsequent PRs. #### Link to tracking issue #35207 #### Testing Added basic tests and built the component to test that the health check endpoint, when tracing is enabled, operates correctly. #### Documentation Docs how to configure the Gitlabreceiver via webhooks have been added. While the Gitlabreceiver can be configured after this PR, it will not actually do anything since it is under development and just the skeleton PR.
1 parent 12551d3 commit 778d8f7

23 files changed

+1167
-0
lines changed
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: new_component
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: gitlabreceiver
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Adds webhook skeleton to GitLab receiver to receive events from GitLab for tracing.
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [35207]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
This PR adds a skeleton for the GitLab receiver to receive events from GitLab
20+
for tracing via a webhook. The trace portion of this receiver will run and
21+
respond to GET requests for the health check only.
22+
23+
# If your change doesn't affect end users or the exported elements of any package,
24+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
25+
# Optional: The change log or logs in which this entry should be included.
26+
# e.g. '[user]' or '[user, api]'
27+
# Include 'user' if the change is relevant to end users.
28+
# Include 'api' if there is a change to a library API.
29+
# Default: '[user]'
30+
change_logs: [user]

.github/CODEOWNERS

+1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ receiver/filestatsreceiver/ @open-telemetry/collector-cont
226226
receiver/flinkmetricsreceiver/ @open-telemetry/collector-contrib-approvers @JonathanWamsley
227227
receiver/fluentforwardreceiver/ @open-telemetry/collector-contrib-approvers @dmitryax
228228
receiver/githubreceiver/ @open-telemetry/collector-contrib-approvers @adrielp @andrzej-stencel @crobert-1 @TylerHelmuth
229+
receiver/gitlabreceiver/ @open-telemetry/collector-contrib-approvers @adrielp @atoulme
229230
receiver/googlecloudmonitoringreceiver/ @open-telemetry/collector-contrib-approvers @dashpole @TylerHelmuth @abhishek-at-cloudwerx
230231
receiver/googlecloudpubsubreceiver/ @open-telemetry/collector-contrib-approvers @alexvanboxel
231232
receiver/googlecloudspannerreceiver/ @open-telemetry/collector-contrib-approvers @dashpole @dsimil @KiranmayiB @harishbohara11

.github/ISSUE_TEMPLATE/bug_report.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ body:
223223
- receiver/flinkmetrics
224224
- receiver/fluentforward
225225
- receiver/github
226+
- receiver/gitlab
226227
- receiver/googlecloudmonitoring
227228
- receiver/googlecloudpubsub
228229
- receiver/googlecloudspanner

.github/ISSUE_TEMPLATE/feature_request.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ body:
217217
- receiver/flinkmetrics
218218
- receiver/fluentforward
219219
- receiver/github
220+
- receiver/gitlab
220221
- receiver/googlecloudmonitoring
221222
- receiver/googlecloudpubsub
222223
- receiver/googlecloudspanner

.github/ISSUE_TEMPLATE/other.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ body:
217217
- receiver/flinkmetrics
218218
- receiver/fluentforward
219219
- receiver/github
220+
- receiver/gitlab
220221
- receiver/googlecloudmonitoring
221222
- receiver/googlecloudpubsub
222223
- receiver/googlecloudspanner

.github/ISSUE_TEMPLATE/unmaintained.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ body:
222222
- receiver/flinkmetrics
223223
- receiver/fluentforward
224224
- receiver/github
225+
- receiver/gitlab
225226
- receiver/googlecloudmonitoring
226227
- receiver/googlecloudpubsub
227228
- receiver/googlecloudspanner

receiver/gitlabreceiver/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ../../Makefile.Common

receiver/gitlabreceiver/README.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# GitLab Receiver
2+
3+
<!-- status autogenerated section -->
4+
| Status | |
5+
| ------------- |-----------|
6+
| Stability | [development]: traces |
7+
| Distributions | [] |
8+
| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Fgitlab%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fgitlab) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Fgitlab%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Fgitlab) |
9+
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@adrielp](https://www.github.com/adrielp), [@atoulme](https://www.github.com/atoulme) |
10+
11+
[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
12+
<!-- end autogenerated section -->
13+
14+
## Traces - Getting Started
15+
16+
Workflow tracing support is actively being added to the GitLab receiver.
17+
This is accomplished through the processing of GitLab webhook
18+
events for pipelines. The [`pipeline`](https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#pipeline-events) event payloads are then constructed into `trace`
19+
telemetry.
20+
21+
Each GitLab pipeline, along with its jobs, is converted
22+
into trace spans, allowing the observation of workflow execution times,
23+
success, and failure rates.
24+
25+
### Configuration
26+
27+
**IMPORTANT: At this time the tracing portion of this receiver only serves a health check endpoint.**
28+
29+
The WebHook configuration exposes the following settings:
30+
31+
* `endpoint`: (default = `localhost:8080`) - The address and port to bind the WebHook to.
32+
* `path`: (default = `/events`) - The path for Action events to be sent to.
33+
* `health_path`: (default = `/health`) - The path for health checks.
34+
* `secret`: (optional) - The secret used to [validate the payload](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#custom-headers).
35+
* `required_headers`: (optional) - One or more key-value pairs representing required headers for incoming requests. These headers must not conflict with the fixed default GitLab headers. See the customizable and fixed GitLab headers in [config.go](./config.go).
36+
37+
The WebHook configuration block also accepts all the [confighttp](https://pkg.go.dev/go.opentelemetry.io/collector/config/confighttp#ServerConfig)
38+
settings.
39+
40+
An example configuration is as follows:
41+
42+
```yaml
43+
receivers:
44+
gitlab:
45+
webhook:
46+
endpoint: localhost:19418
47+
path: /events
48+
health_path: /health
49+
secret: ${env:SECRET_STRING_VAR}
50+
required_headers:
51+
WAF-Header: "value"
52+
```
53+
54+
For tracing, all configuration is set under the `webhook` key. The full set
55+
of exposed configuration values can be found in [`config.go`](config.go).

receiver/gitlabreceiver/config.go

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package gitlabreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/gitlabreceiver"
5+
6+
import (
7+
"errors"
8+
"time"
9+
10+
"go.opentelemetry.io/collector/component"
11+
"go.opentelemetry.io/collector/config/confighttp"
12+
"go.opentelemetry.io/collector/config/configopaque"
13+
"go.opentelemetry.io/collector/confmap"
14+
"go.uber.org/multierr"
15+
)
16+
17+
const (
18+
defaultReadTimeout = 500 * time.Millisecond
19+
defaultWriteTimeout = 500 * time.Millisecond
20+
21+
defaultEndpoint = "localhost:8080"
22+
23+
defaultPath = "/events"
24+
defaultHealthPath = "/health"
25+
26+
// GitLab default headers: https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#delivery-headers
27+
defaultUserAgentHeader = "User-Agent"
28+
defaultGitlabInstanceHeader = "X-Gitlab-Instance"
29+
defaultGitlabWebhookUUIDHeader = "X-Gitlab-Webhook-UUID"
30+
defaultGitlabEventHeader = "X-Gitlab-Event"
31+
defaultGitlabEventUUIDHeader = "X-Gitlab-Event-UUID"
32+
defaultIdempotencyKeyHeader = "Idempotency-Key"
33+
)
34+
35+
var (
36+
errReadTimeoutExceedsMaxValue = errors.New("the duration specified for read_timeout exceeds the maximum allowed value of 10s")
37+
errWriteTimeoutExceedsMaxValue = errors.New("the duration specified for write_timeout exceeds the maximum allowed value of 10s")
38+
errRequiredHeader = errors.New("both key and value are required to assign a required_header")
39+
errGitlabHeader = errors.New("gitlab default headers [X-Gitlab-Webhook-UUID, X-Gitlab-Event, X-Gitlab-Event-UUID, Idempotency-Key] cannot be configured")
40+
errConfigNotValid = errors.New("configuration is not valid for the gitlab receiver")
41+
)
42+
43+
// Config that is exposed to this gitlab receiver through the OTEL config.yaml
44+
type Config struct {
45+
WebHook WebHook `mapstructure:"webhook"`
46+
}
47+
48+
type WebHook struct {
49+
confighttp.ServerConfig `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
50+
51+
Path string `mapstructure:"path"` // path for data collection. default is /events
52+
HealthPath string `mapstructure:"health_path"` // path for health check api. default is /health_check
53+
54+
RequiredHeaders map[string]configopaque.String `mapstructure:"required_headers"` // optional setting to set one or more required headers for all requests to have (except the health check)
55+
GitlabHeaders GitlabHeaders `mapstructure:",squash"` // GitLab headers set by default
56+
57+
Secret string `mapstructure:"secret"` // secret for webhook
58+
}
59+
60+
type GitlabHeaders struct {
61+
Customizable map[string]string `mapstructure:","` // can be overwritten via required_headers
62+
Fixed map[string]string `mapstructure:","` // are not allowed to be overwritten
63+
}
64+
65+
func createDefaultConfig() component.Config {
66+
return &Config{
67+
WebHook: WebHook{
68+
ServerConfig: confighttp.ServerConfig{
69+
Endpoint: defaultEndpoint,
70+
ReadTimeout: defaultReadTimeout,
71+
WriteTimeout: defaultWriteTimeout,
72+
},
73+
GitlabHeaders: GitlabHeaders{
74+
Customizable: map[string]string{
75+
defaultUserAgentHeader: "",
76+
defaultGitlabInstanceHeader: "https://gitlab.com",
77+
},
78+
Fixed: map[string]string{
79+
defaultGitlabWebhookUUIDHeader: "",
80+
defaultGitlabEventHeader: "Pipeline Hook",
81+
defaultGitlabEventUUIDHeader: "",
82+
defaultIdempotencyKeyHeader: "",
83+
},
84+
},
85+
Path: defaultPath,
86+
HealthPath: defaultHealthPath,
87+
},
88+
}
89+
}
90+
91+
func (cfg *Config) Validate() error {
92+
var errs error
93+
94+
maxReadWriteTimeout, _ := time.ParseDuration("10s")
95+
96+
if cfg.WebHook.ServerConfig.ReadTimeout > maxReadWriteTimeout {
97+
errs = multierr.Append(errs, errReadTimeoutExceedsMaxValue)
98+
}
99+
100+
if cfg.WebHook.ServerConfig.WriteTimeout > maxReadWriteTimeout {
101+
errs = multierr.Append(errs, errWriteTimeoutExceedsMaxValue)
102+
}
103+
104+
for key, value := range cfg.WebHook.RequiredHeaders {
105+
if key == "" || value == "" {
106+
errs = multierr.Append(errs, errRequiredHeader)
107+
}
108+
109+
if _, exists := cfg.WebHook.GitlabHeaders.Fixed[key]; exists {
110+
errs = multierr.Append(errs, errGitlabHeader)
111+
}
112+
}
113+
114+
return errs
115+
}
116+
117+
func (cfg *Config) Unmarshal(componentParser *confmap.Conf) error {
118+
if componentParser == nil {
119+
return nil
120+
}
121+
122+
// load the non-dynamic config normally
123+
err := componentParser.Unmarshal(cfg, confmap.WithIgnoreUnused())
124+
if err != nil {
125+
return err
126+
}
127+
128+
// overwrite customizable GitLab default headers if configured within the required_headers
129+
for key, header := range cfg.WebHook.RequiredHeaders {
130+
if _, exists := cfg.WebHook.GitlabHeaders.Customizable[key]; exists {
131+
cfg.WebHook.GitlabHeaders.Customizable[key] = string(header)
132+
}
133+
}
134+
135+
return nil
136+
}
+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package gitlabreceiver
5+
6+
import (
7+
"path/filepath"
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
"go.opentelemetry.io/collector/component"
14+
"go.opentelemetry.io/collector/component/componenttest"
15+
"go.opentelemetry.io/collector/config/confighttp"
16+
"go.opentelemetry.io/collector/config/configopaque"
17+
"go.opentelemetry.io/collector/otelcol/otelcoltest"
18+
19+
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/gitlabreceiver/internal/metadata"
20+
)
21+
22+
func TestCreateDefaultConfig(t *testing.T) {
23+
factory := NewFactory()
24+
cfg := factory.CreateDefaultConfig()
25+
26+
expectedConfig := &Config{
27+
WebHook: WebHook{
28+
ServerConfig: confighttp.ServerConfig{
29+
Endpoint: defaultEndpoint,
30+
ReadTimeout: defaultReadTimeout,
31+
WriteTimeout: defaultWriteTimeout,
32+
},
33+
Path: defaultPath,
34+
HealthPath: defaultHealthPath,
35+
GitlabHeaders: GitlabHeaders{
36+
Customizable: map[string]string{
37+
defaultUserAgentHeader: "",
38+
defaultGitlabInstanceHeader: "https://gitlab.com",
39+
},
40+
Fixed: map[string]string{
41+
defaultGitlabWebhookUUIDHeader: "",
42+
defaultGitlabEventHeader: "Pipeline Hook",
43+
defaultGitlabEventUUIDHeader: "",
44+
defaultIdempotencyKeyHeader: "",
45+
},
46+
},
47+
},
48+
}
49+
50+
assert.Equal(t, expectedConfig, cfg, "failed to create default config")
51+
assert.NoError(t, componenttest.CheckConfigStruct(cfg))
52+
}
53+
54+
func TestLoadConfig(t *testing.T) {
55+
factories, err := otelcoltest.NopFactories()
56+
require.NoError(t, err)
57+
58+
factory := NewFactory()
59+
factories.Receivers[metadata.Type] = factory
60+
61+
cfg, err := otelcoltest.LoadConfigAndValidate(filepath.Join("testdata", "config.yaml"), factories)
62+
63+
require.NoError(t, err)
64+
require.NotNil(t, cfg)
65+
66+
assert.Len(t, cfg.Receivers, 2)
67+
68+
expectedConfig := &Config{
69+
WebHook: WebHook{
70+
ServerConfig: confighttp.ServerConfig{
71+
Endpoint: "localhost:8080",
72+
ReadTimeout: 500 * time.Millisecond,
73+
WriteTimeout: 500 * time.Millisecond,
74+
},
75+
Path: "some/path",
76+
HealthPath: "health/path",
77+
RequiredHeaders: map[string]configopaque.String{
78+
"key1-present": "value1-present",
79+
},
80+
GitlabHeaders: GitlabHeaders{
81+
Customizable: map[string]string{
82+
defaultUserAgentHeader: "",
83+
defaultGitlabInstanceHeader: "https://gitlab.com",
84+
},
85+
Fixed: map[string]string{
86+
defaultGitlabWebhookUUIDHeader: "",
87+
defaultGitlabEventHeader: "Pipeline Hook",
88+
defaultGitlabEventUUIDHeader: "",
89+
defaultIdempotencyKeyHeader: "",
90+
},
91+
},
92+
},
93+
}
94+
95+
r0 := cfg.Receivers[component.NewID(metadata.Type)]
96+
97+
assert.Equal(t, expectedConfig, r0)
98+
99+
// r1 requires multiple headers and overwrites gitlab default headers
100+
expectedConfig.WebHook.RequiredHeaders = map[string]configopaque.String{
101+
"key1-present": "value1-present",
102+
"key2-present": "value2-present",
103+
"User-Agent": "GitLab/1.2.3-custom-version",
104+
"X-Gitlab-Instance": "https://gitlab.self-hosted.xyz",
105+
}
106+
107+
expectedConfig.WebHook.GitlabHeaders = GitlabHeaders{
108+
Customizable: map[string]string{
109+
defaultUserAgentHeader: "GitLab/1.2.3-custom-version",
110+
defaultGitlabInstanceHeader: "https://gitlab.self-hosted.xyz",
111+
},
112+
Fixed: map[string]string{
113+
defaultGitlabWebhookUUIDHeader: "",
114+
defaultGitlabEventHeader: "Pipeline Hook",
115+
defaultGitlabEventUUIDHeader: "",
116+
defaultIdempotencyKeyHeader: "",
117+
},
118+
}
119+
120+
r1 := cfg.Receivers[component.NewIDWithName(metadata.Type, "customname")].(*Config)
121+
122+
assert.Equal(t, expectedConfig, r1)
123+
}

receiver/gitlabreceiver/doc.go

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//go:generate mdatagen metadata.yaml
5+
6+
package gitlabreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/gitlabreceiver"

0 commit comments

Comments
 (0)