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 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
325a0d5
wip: model additions
adrielp Jan 12, 2025
4cacce0
wip: add referenced version and additional non-working changes
adrielp Jan 12, 2025
7302643
chore: small addition and changes to model, in prep for switch logic
adrielp Jan 20, 2025
d164f09
chore: commit start of switch
adrielp Jan 20, 2025
c1849cf
[chore] more work
adrielp Jan 29, 2025
b4a4a8f
[test] add newTraceID and newParentSpanID tests
adrielp Jan 29, 2025
f6edd9d
[test] add TestHandleWorkflowRun test
adrielp Jan 29, 2025
f4b4f8b
[chore] run checks and fix lints
adrielp Jan 29, 2025
95a160f
Merge branch 'main' into gh-tracing
adrielp Jan 29, 2025
a47d810
[chore] add changelog
adrielp Jan 29, 2025
8b2f03b
[chore] add template variables in model
adrielp Jan 30, 2025
1aedf0c
[chore] fix removed var declaration
adrielp Jan 30, 2025
0fb1286
[chore] add reference workflow and previous attempt attrs
adrielp Jan 31, 2025
0184a71
[chore] small changes
adrielp Jan 31, 2025
bedfbaf
Merge remote-tracking branch 'origin/main' into gh-tracing
adrielp Jan 31, 2025
982ca2f
[chore] update header configuration
adrielp Feb 2, 2025
b3ec694
[chore] update the readme
adrielp Feb 3, 2025
a8b9b58
[chore] update the readme about workflow events
adrielp Feb 3, 2025
9980832
Merge branch 'main' into gh-tracing
adrielp Feb 3, 2025
c455735
[chore] fix typo in test
adrielp Feb 3, 2025
5727ddc
[chore] remove unused files
adrielp Feb 3, 2025
a839940
[chore] fix up readme and logs
adrielp Feb 4, 2025
8682036
Merge branch 'main' into gh-tracing
adrielp Feb 4, 2025
90423d2
[chore] tidy after merge from main
adrielp Feb 4, 2025
3bcf895
Merge branch 'main' into gh-tracing
adrielp Feb 5, 2025
14f182a
[chore] changes from pr review
adrielp Feb 6, 2025
94ba17a
Merge branch 'main' into gh-tracing
adrielp Feb 6, 2025
3963c9c
[chore] update changelog with breaking change
adrielp Feb 6, 2025
e893fed
Merge branch 'main' into gh-tracing
adrielp Feb 6, 2025
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: []
84 changes: 65 additions & 19 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,54 @@ 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.

Copy link
Member

Choose a reason for hiding this comment

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

That makes sense. I'd say it's better to error on the side of caution in this case. Anytime a config changes it's pretty disconcerting for users, so I'd say make it as obvious and clear to users as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I'll update the change log so that it shows it as a breaking change

```

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].
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` option in the WebHook configuration can be used to set 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 precedence for `service.name` generation is as follows:

1. `service_name` configuration in the WebHook configuration.
2. `service_name` key in the repository's Custom Properties per repository.
3. `service_name` derived from the repository name.
4. `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