Skip to content
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

[receiver/gitlab] add tracing via webhook skeleton #36838

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 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
30 changes: 30 additions & 0 deletions .chloggen/gl-receiver-skeleton-traces.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: new_component

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: gitlabreceiver

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Adds webhook skeleton to GitLab receiver to receive events from GitLab for tracing.

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [35207]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
This PR adds a skeleton for the GitLab receiver to receive events from GitLab
for tracing via a webhook. The trace portion of this receiver will run and
respond to GET requests for the health check only.

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ receiver/filestatsreceiver/ @open-telemetry/collector-cont
receiver/flinkmetricsreceiver/ @open-telemetry/collector-contrib-approvers @JonathanWamsley
receiver/fluentforwardreceiver/ @open-telemetry/collector-contrib-approvers @dmitryax
receiver/githubreceiver/ @open-telemetry/collector-contrib-approvers @adrielp @andrzej-stencel @crobert-1 @TylerHelmuth
receiver/gitlabreceiver/ @open-telemetry/collector-contrib-approvers @adrielp @atoulme
niwoerner marked this conversation as resolved.
Show resolved Hide resolved
receiver/googlecloudmonitoringreceiver/ @open-telemetry/collector-contrib-approvers @dashpole @TylerHelmuth @abhishek-at-cloudwerx
receiver/googlecloudpubsubreceiver/ @open-telemetry/collector-contrib-approvers @alexvanboxel
receiver/googlecloudspannerreceiver/ @open-telemetry/collector-contrib-approvers @dashpole @dsimil @KiranmayiB @harishbohara11
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ body:
- receiver/flinkmetrics
- receiver/fluentforward
- receiver/github
- receiver/gitlab
- receiver/googlecloudmonitoring
- receiver/googlecloudpubsub
- receiver/googlecloudspanner
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ body:
- receiver/flinkmetrics
- receiver/fluentforward
- receiver/github
- receiver/gitlab
- receiver/googlecloudmonitoring
- receiver/googlecloudpubsub
- receiver/googlecloudspanner
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/other.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ body:
- receiver/flinkmetrics
- receiver/fluentforward
- receiver/github
- receiver/gitlab
- receiver/googlecloudmonitoring
- receiver/googlecloudpubsub
- receiver/googlecloudspanner
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/unmaintained.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ body:
- receiver/flinkmetrics
- receiver/fluentforward
- receiver/github
- receiver/gitlab
- receiver/googlecloudmonitoring
- receiver/googlecloudpubsub
- receiver/googlecloudspanner
Expand Down
1 change: 1 addition & 0 deletions receiver/gitlabreceiver/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
55 changes: 55 additions & 0 deletions receiver/gitlabreceiver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# GitLab Receiver

<!-- status autogenerated section -->
| Status | |
| ------------- |-----------|
| Stability | [development]: traces |
| Distributions | [] |
| 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) |
| [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) |

[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
<!-- end autogenerated section -->

## Traces - Getting Started

Workflow tracing support is actively being added to the GitLab receiver.
This is accomplished through the processing of GitLab webhook
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`
telemetry.

Each GitLab pipeline, along with its jobs, is converted
into trace spans, allowing the observation of workflow execution times,
success, and failure rates.

### Configuration

**IMPORTANT: At this time the tracing portion of this receiver only serves a health check endpoint.**

The WebHook configuration exposes the following settings:

* `endpoint`: (default = `localhost:8080`) - The address and port to bind the WebHook to.
* `path`: (default = `/events`) - The path for Action events to be sent to.
* `health_path`: (default = `/health`) - The path for health checks.
* `secret`: (optional) - The secret used to [validate the payload](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#custom-headers).
* `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).

The WebHook configuration block also accepts all the [confighttp](https://pkg.go.dev/go.opentelemetry.io/collector/config/confighttp#ServerConfig)
settings.

An example configuration is as follows:

```yaml
receivers:
gitlab:
webhook:
endpoint: localhost:19418
path: /events
health_path: /health
secret: ${env:SECRET_STRING_VAR}
required_headers:
WAF-Header: "value"
```

For tracing, all configuration is set under the `webhook` key. The full set
of exposed configuration values can be found in [`config.go`](config.go).
136 changes: 136 additions & 0 deletions receiver/gitlabreceiver/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package gitlabreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/gitlabreceiver"

import (
"errors"
"time"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/confmap"
"go.uber.org/multierr"
)

const (
defaultReadTimeout = 500 * time.Millisecond
defaultWriteTimeout = 500 * time.Millisecond

defaultEndpoint = "localhost:8080"

defaultPath = "/events"
defaultHealthPath = "/health"

// GitLab default headers: https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#delivery-headers
defaultUserAgentHeader = "User-Agent"
defaultGitlabInstanceHeader = "X-Gitlab-Instance"
defaultGitlabWebhookUUIDHeader = "X-Gitlab-Webhook-UUID"
defaultGitlabEventHeader = "X-Gitlab-Event"
defaultGitlabEventUUIDHeader = "X-Gitlab-Event-UUID"
defaultIdempotencyKeyHeader = "Idempotency-Key"
)

var (
errReadTimeoutExceedsMaxValue = errors.New("the duration specified for read_timeout exceeds the maximum allowed value of 10s")
errWriteTimeoutExceedsMaxValue = errors.New("the duration specified for write_timeout exceeds the maximum allowed value of 10s")
errRequiredHeader = errors.New("both key and value are required to assign a required_header")
errGitlabHeader = errors.New("gitlab default headers [X-Gitlab-Webhook-UUID, X-Gitlab-Event, X-Gitlab-Event-UUID, Idempotency-Key] cannot be configured")
errConfigNotValid = errors.New("configuration is not valid for the gitlab receiver")
)

// Config that is exposed to this gitlab receiver through the OTEL config.yaml
type Config struct {
WebHook WebHook `mapstructure:"webhook"`
}

type WebHook struct {
confighttp.ServerConfig `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct

Path string `mapstructure:"path"` // path for data collection. default is /events
HealthPath string `mapstructure:"health_path"` // path for health check api. default is /health_check

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)
GitlabHeaders GitlabHeaders `mapstructure:",squash"` // GitLab headers set by default

Secret string `mapstructure:"secret"` // secret for webhook
}

type GitlabHeaders struct {
Customizable map[string]string `mapstructure:","` // can be overwritten via required_headers
Fixed map[string]string `mapstructure:","` // are not allowed to be overwritten
}

func createDefaultConfig() component.Config {
return &Config{
WebHook: WebHook{
ServerConfig: confighttp.ServerConfig{
Endpoint: defaultEndpoint,
ReadTimeout: defaultReadTimeout,
WriteTimeout: defaultWriteTimeout,
},
GitlabHeaders: GitlabHeaders{
Customizable: map[string]string{
defaultUserAgentHeader: "",
defaultGitlabInstanceHeader: "https://gitlab.com",
},
Fixed: map[string]string{
defaultGitlabWebhookUUIDHeader: "",
defaultGitlabEventHeader: "Pipeline Hook",
defaultGitlabEventUUIDHeader: "",
defaultIdempotencyKeyHeader: "",
},
},
Path: defaultPath,
HealthPath: defaultHealthPath,
},
}
}

func (cfg *Config) Validate() error {
var errs error

maxReadWriteTimeout, _ := time.ParseDuration("10s")

if cfg.WebHook.ServerConfig.ReadTimeout > maxReadWriteTimeout {
errs = multierr.Append(errs, errReadTimeoutExceedsMaxValue)
}

if cfg.WebHook.ServerConfig.WriteTimeout > maxReadWriteTimeout {
errs = multierr.Append(errs, errWriteTimeoutExceedsMaxValue)
}

for key, value := range cfg.WebHook.RequiredHeaders {
if key == "" || value == "" {
errs = multierr.Append(errs, errRequiredHeader)
}

if _, exists := cfg.WebHook.GitlabHeaders.Fixed[key]; exists {
errs = multierr.Append(errs, errGitlabHeader)
}
}

return errs
}

func (cfg *Config) Unmarshal(componentParser *confmap.Conf) error {
if componentParser == nil {
return nil
}

// load the non-dynamic config normally
err := componentParser.Unmarshal(cfg, confmap.WithIgnoreUnused())
if err != nil {
return err
}

// overwrite customizable GitLab default headers if configured within the required_headers
for key, header := range cfg.WebHook.RequiredHeaders {
if _, exists := cfg.WebHook.GitlabHeaders.Customizable[key]; exists {
cfg.WebHook.GitlabHeaders.Customizable[key] = string(header)
}
}

return nil
}
123 changes: 123 additions & 0 deletions receiver/gitlabreceiver/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package gitlabreceiver

import (
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/otelcol/otelcoltest"

"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/gitlabreceiver/internal/metadata"
)

func TestCreateDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()

expectedConfig := &Config{
WebHook: WebHook{
ServerConfig: confighttp.ServerConfig{
Endpoint: defaultEndpoint,
ReadTimeout: defaultReadTimeout,
WriteTimeout: defaultWriteTimeout,
},
Path: defaultPath,
HealthPath: defaultHealthPath,
GitlabHeaders: GitlabHeaders{
Customizable: map[string]string{
defaultUserAgentHeader: "",
defaultGitlabInstanceHeader: "https://gitlab.com",
},
Fixed: map[string]string{
defaultGitlabWebhookUUIDHeader: "",
defaultGitlabEventHeader: "Pipeline Hook",
defaultGitlabEventUUIDHeader: "",
defaultIdempotencyKeyHeader: "",
},
},
},
}

assert.Equal(t, expectedConfig, cfg, "failed to create default config")
assert.NoError(t, componenttest.CheckConfigStruct(cfg))
}

func TestLoadConfig(t *testing.T) {
factories, err := otelcoltest.NopFactories()
require.NoError(t, err)

factory := NewFactory()
factories.Receivers[metadata.Type] = factory

cfg, err := otelcoltest.LoadConfigAndValidate(filepath.Join("testdata", "config.yaml"), factories)

require.NoError(t, err)
require.NotNil(t, cfg)

assert.Len(t, cfg.Receivers, 2)

expectedConfig := &Config{
WebHook: WebHook{
ServerConfig: confighttp.ServerConfig{
Endpoint: "localhost:8080",
ReadTimeout: 500 * time.Millisecond,
WriteTimeout: 500 * time.Millisecond,
},
Path: "some/path",
HealthPath: "health/path",
RequiredHeaders: map[string]configopaque.String{
"key1-present": "value1-present",
},
GitlabHeaders: GitlabHeaders{
Customizable: map[string]string{
defaultUserAgentHeader: "",
defaultGitlabInstanceHeader: "https://gitlab.com",
},
Fixed: map[string]string{
defaultGitlabWebhookUUIDHeader: "",
defaultGitlabEventHeader: "Pipeline Hook",
defaultGitlabEventUUIDHeader: "",
defaultIdempotencyKeyHeader: "",
},
},
},
}

r0 := cfg.Receivers[component.NewID(metadata.Type)]

assert.Equal(t, expectedConfig, r0)

// r1 requires multiple headers and overwrites gitlab default headers
expectedConfig.WebHook.RequiredHeaders = map[string]configopaque.String{
"key1-present": "value1-present",
"key2-present": "value2-present",
"User-Agent": "GitLab/1.2.3-custom-version",
"X-Gitlab-Instance": "https://gitlab.self-hosted.xyz",
}

expectedConfig.WebHook.GitlabHeaders = GitlabHeaders{
Customizable: map[string]string{
defaultUserAgentHeader: "GitLab/1.2.3-custom-version",
defaultGitlabInstanceHeader: "https://gitlab.self-hosted.xyz",
},
Fixed: map[string]string{
defaultGitlabWebhookUUIDHeader: "",
defaultGitlabEventHeader: "Pipeline Hook",
defaultGitlabEventUUIDHeader: "",
defaultIdempotencyKeyHeader: "",
},
}

r1 := cfg.Receivers[component.NewIDWithName(metadata.Type, "customname")].(*Config)

assert.Equal(t, expectedConfig, r1)
}
6 changes: 6 additions & 0 deletions receiver/gitlabreceiver/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

//go:generate mdatagen metadata.yaml

package gitlabreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/gitlabreceiver"
Loading
Loading