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/github] add workflow run event trace handling #37578

Open
wants to merge 21 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
27 changes: 27 additions & 0 deletions .chloggen/gh-tracing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

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

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

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: add support for GitHub Actions workflow run events using deterministic Trace and Root Span IDs.

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

# (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:

# 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: []
82 changes: 65 additions & 17 deletions receiver/githubreceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,26 @@
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
<!-- end autogenerated section -->

The GitHub receiver receives data from [GitHub](https://github.com).
# Table of Contents

- [Overview](#overview)
- [Metrics - Getting Started](#metrics---getting-started)
- [Scraping](#scraping)
- [Traces - Getting Started](#traces---getting-started)
- [Receiver Configuration](#receiver-configuration)
- [Configuring Service Name](#configuring-service-name)
- [Configuration a GitHub App](#configuration-a-github-app)

## Overview

The GitHub receiver receives data from [GitHub](https://github.com) via two methods:

1. Scrapes [version control system][vcsm] metrics from GitHub repositories and
adrielp marked this conversation as resolved.
Show resolved Hide resolved
organizations using the GraphQL and REST APIs.
2. Receives GitHub Actions events by serving a webhook endpoint, converting
those events into traces.

## Metrics - Getting Started

The current default set of metrics can be found in
[documentation.md](./documentation.md).
Expand All @@ -23,11 +42,6 @@ These metrics can be used as leading indicators ([capabilities][doracap])
to the [DORA][dorafour] metrics; helping provide insight into modern-day
engineering practices.

[doracap]: https://dora.dev/capabilities/
[dorafour]: https://dora.dev/guides/dora-metrics-four-keys/

## Metrics - Getting Started

The collection interval is common to all scrapers and is set to 30 seconds by default.

> Note: Generally speaking, if the vendor allows for anonymous API calls, then you
Expand Down Expand Up @@ -75,17 +89,14 @@ service:

A [Grafana Dashboard for metrics from this receiver is on the marketplace](https://grafana.com/grafana/dashboards/20976-engineering-effectiveness-metrics/).

## Scraping
### Scraping

> Important:
> * The GitHub scraper does not emit metrics for branches that have not had
> changes since creation from the default branch (trunk).
> * Due to GitHub API limitations, it is possible for the branch time metric to
> change when rebases occur, recreating the commits with new timestamps.


<!-- TODO: Combine this documentation once the scraper code is restructured due scope change -->

For additional context on GitHub scraper limitations and inner workings please
see the [Scraping README][ghsread].

Expand All @@ -103,9 +114,10 @@ Each GitHub Action workflow or job, along with its steps, are converted
into trace spans, allowing the observation of workflow execution times,
success, and failure rates.

### Configuration
### Receiver Configuration

**IMPORTANT: At this time the tracing portion of this receiver only serves a health check endpoint.**
**IMPORTANT** - Ensure to secure your WebHook endpoint with a secret and a Web
Application Firewall (WAF) or other security measure.

The WebHook configuration exposes the following settings:

Expand All @@ -114,6 +126,9 @@ The WebHook configuration exposes the following settings:
* `health_path`: (default = `/health`) - The path for health checks.
* `secret`: (optional) - The secret used to [validates the payload][valid].
* `required_header`: (optional) - The required header key and value for incoming requests.
* `service_name`: (optional) - The service name for the traces. See the
[Configuring Service Name](#configuring-service-name) section for more
information.

The WebHook configuration block also accepts all the [confighttp][cfghttp]
settings.
Expand All @@ -123,23 +138,56 @@ An example configuration is as follows:
```yaml
receivers:
github:
scrapers:
... <scraper configuration>: # Scraper configurations are required until Tracing functionality is complete.
webhook:
endpoint: localhost:19418
path: /events
health_path: /health
secret: ${env:SECRET_STRING_VAR}
required_header:
key: "X-GitHub-Event"
value: "action"
required_headers:
WAF-Header: "value"
Comment on lines +146 to +147
Copy link
Member

Choose a reason for hiding this comment

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

Since the configuration format is changing, shouldn't this be considered a breaking change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well the functionality of this portion of the receiver has only been serving a health check endpoint and is marked "development." The previous config value wouldn't have worked anyway since this is not of the health check endpoint, and only for the events portion. I guess I'm saying, there was nothing to break, since there wasn't anything that "worked." But happy to mark it if necessary in the change log for sanity.

```

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].


adrielp marked this conversation as resolved.
Show resolved Hide resolved
### Configuring Service Name

The `service_name` configuration in the WebHook configuration can be used to set
adrielp marked this conversation as resolved.
Show resolved Hide resolved
a pre-defined `service.name` for all traces emitted by the receiver. This takes
priority over the internal generation of the `service.name`. In this
configuration, it would be important to create a GitHub receiver per GitHub app
configured for the set of repositories that match your `service.name`.

However, a more efficient approach would be to leverage the default generation
of `service.name` by configuring [Custom Properties][cp] in each GitHub
repository. To do that simply add a `service_name` key with the desired value in
each repository and all events sent to the GitHub receiver will properly
associate with that `service.name`. Alternatively, the `service_name` will be
derived from the repository name.

The order for creating the `service.name` is as follows:
adrielp marked this conversation as resolved.
Show resolved Hide resolved

* `service_name` configuration in the WebHook configuration.
* `service_name` key in the repository's Custom Properties per repository.
* `service_name` derived from the repository name.
* `service.name` set to `unknown_service` per the semantic conventions as a fall back.

### Configuring A GitHub App

To configure a GitHub App, you will need to create a new GitHub App within your
organization. Refer to the general [GitHub App documentation][ghapp] for how to
create a GitHub App. During the subscription phase, subscribe to `workflow_run` and `workflow_job` events.

> NOTE: Only `workflow_run` events are supported in created traces at this time.

[wjob]: https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_job
[wrun]: https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_run
[valid]: https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
[config.go] ./config.go
[cfghttp]: https://pkg.go.dev/go.opentelemetry.io/collector/config/confighttp#ServerConfig
[cp]: https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization
[vcsm]: https://opentelemetry.io/docs/specs/semconv/cicd/cicd-metrics/#vcs-metrics
[doracap]: https://dora.dev/capabilities/
[dorafour]: https://dora.dev/guides/dora-metrics-four-keys/
[ghapp]: https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app
37 changes: 27 additions & 10 deletions receiver/githubreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/scraper/scraperhelper"
"go.uber.org/multierr"
Expand All @@ -20,6 +21,13 @@ import (

const (
scrapersKey = "scrapers"

// GitHub Delivery Headers: https://docs.github.com/en/webhooks/webhook-events-and-payloads#delivery-headers
defaultGitHubHookIDHeader = "X-GitHub-Hook-ID" // Unique identifier of the webhook.
defaultGitHubEventHeader = "X-GitHub-Event" // The name of the event that triggered the delivery.
defaultGitHubDeliveryHeader = "X-GitHub-Delivery" // A globally unique identifier (GUID) to identify the event.
defaultGitHubSignature256Header = "X-Hub-Signature-256" // The HMAC hex digest of the request body; generated using the SHA-256 hash function and the secret as the HMAC key.
defaultUserAgentHeader = "User-Agent" // Value always prefixed with "GitHub-Hookshot/"
)

// Config that is exposed to this github receiver through the OTEL config.yaml
Expand All @@ -31,16 +39,18 @@ type Config struct {
}

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
RequiredHeader RequiredHeader `mapstructure:"required_header"` // optional setting to set a required header for all requests to have
Secret string `mapstructure:"secret"` // secret for webhook
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)
GitHubHeaders GitHubHeaders `mapstructure:",squash"` // GitLab headers set by default
Secret string `mapstructure:"secret"` // secret for webhook
ServiceName string `mapstructure:"service_name"`
}

type RequiredHeader struct {
Key string `mapstructure:"key"`
Value string `mapstructure:"value"`
type GitHubHeaders struct {
Customizable map[string]string `mapstructure:","` // can be overwritten via required_headers
Fixed map[string]string `mapstructure:","` // are not allowed to be overwritten
}

var (
Expand All @@ -52,6 +62,7 @@ var (
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")
errRequireOneScraper = errors.New("must specify at least one scraper")
errGitHubHeader = errors.New("github default headers [X-GitHub-Event, X-GitHub-Delivery, X-GitHub-Hook-ID, X-Hub-Signature-256] cannot be configured")
)

// Validate the configuration passed through the OTEL config.yaml
Expand All @@ -78,8 +89,14 @@ func (cfg *Config) Validate() error {
errs = multierr.Append(errs, errWriteTimeoutExceedsMaxValue)
}

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

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

return errs
Expand Down
33 changes: 27 additions & 6 deletions receiver/githubreceiver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configopaque"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/otelcol/otelcoltest"
"go.opentelemetry.io/collector/scraper/scraperhelper"
Expand Down Expand Up @@ -49,9 +50,19 @@ func TestLoadConfig(t *testing.T) {
},
Path: "some/path",
HealthPath: "health/path",
RequiredHeader: RequiredHeader{
Key: "key-present",
Value: "value-present",
RequiredHeaders: map[string]configopaque.String{
"key": "value-present",
},
GitHubHeaders: GitHubHeaders{
Customizable: map[string]string{
"User-Agent": "",
},
Fixed: map[string]string{
"X-GitHub-Delivery": "",
"X-GitHub-Event": "",
"X-GitHub-Hook-ID": "",
"X-Hub-Signature-256": "",
},
},
}

Expand All @@ -74,9 +85,19 @@ func TestLoadConfig(t *testing.T) {
},
Path: "some/path",
HealthPath: "health/path",
RequiredHeader: RequiredHeader{
Key: "key-present",
Value: "value-present",
RequiredHeaders: map[string]configopaque.String{
"key": "value-present",
},
GitHubHeaders: GitHubHeaders{
Customizable: map[string]string{
"User-Agent": "",
},
Fixed: map[string]string{
"X-GitHub-Delivery": "",
"X-GitHub-Event": "",
"X-GitHub-Hook-ID": "",
"X-Hub-Signature-256": "",
},
},
},
}
Expand Down
11 changes: 11 additions & 0 deletions receiver/githubreceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ func createDefaultConfig() component.Config {
ReadTimeout: defaultReadTimeout,
WriteTimeout: defaultWriteTimeout,
},
GitHubHeaders: GitHubHeaders{
Customizable: map[string]string{
defaultUserAgentHeader: "",
},
Fixed: map[string]string{
defaultGitHubEventHeader: "",
defaultGitHubDeliveryHeader: "",
defaultGitHubHookIDHeader: "",
defaultGitHubSignature256Header: "",
},
},
Path: defaultPath,
HealthPath: defaultHealthPath,
},
Expand Down
4 changes: 2 additions & 2 deletions receiver/githubreceiver/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
go.opentelemetry.io/collector/component/componentstatus v0.118.1-0.20250131104636-a737a48402e0
go.opentelemetry.io/collector/component/componenttest v0.118.1-0.20250131104636-a737a48402e0
go.opentelemetry.io/collector/config/confighttp v0.118.1-0.20250131104636-a737a48402e0
go.opentelemetry.io/collector/config/configopaque v1.24.1-0.20250131104636-a737a48402e0
go.opentelemetry.io/collector/confmap v1.24.1-0.20250131104636-a737a48402e0
go.opentelemetry.io/collector/consumer v1.24.1-0.20250131104636-a737a48402e0
go.opentelemetry.io/collector/consumer/consumertest v0.118.1-0.20250131104636-a737a48402e0
Expand Down Expand Up @@ -75,13 +76,12 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/vektah/gqlparser/v2 v2.5.20 // indirect
github.com/vektah/gqlparser/v2 v2.5.22 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/collector/client v1.24.1-0.20250131104636-a737a48402e0 // indirect
go.opentelemetry.io/collector/config/configauth v0.118.1-0.20250131104636-a737a48402e0 // indirect
go.opentelemetry.io/collector/config/configcompression v1.24.1-0.20250131104636-a737a48402e0 // indirect
go.opentelemetry.io/collector/config/configopaque v1.24.1-0.20250131104636-a737a48402e0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v0.118.1-0.20250131104636-a737a48402e0 // indirect
go.opentelemetry.io/collector/config/configtls v1.24.1-0.20250131104636-a737a48402e0 // indirect
go.opentelemetry.io/collector/confmap/provider/envprovider v1.24.1-0.20250131104636-a737a48402e0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions receiver/githubreceiver/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading