Skip to content

Commit 86a81d3

Browse files
authored
[receiver/github] add tracing via webhook skeleton (open-telemetry#36632)
#### Description Adds the basic webhook configuration and logic, with a health check, to enable development of tracings (and logs) in future iterations. #### Testing Added basic tests and built the component to test that the health check endpoint, when tracing is enabled, operates correctly. #### Documentation Because this portion of the receiver is in development, and adds only the skeleton, no docs have been added yet.
1 parent 396c63d commit 86a81d3

15 files changed

+439
-45
lines changed

Diff for: .chloggen/gh-trace-skeleton.yaml

+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: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: githubreceiver
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 GitHub receiver to receive events from GitHub 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: [27460]
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 GitHub receiver to receive events from GitHub
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]

Diff for: receiver/githubreceiver/README.md

+59-6
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
<!-- status autogenerated section -->
44
| Status | |
55
| ------------- |-----------|
6-
| Stability | [alpha]: metrics |
6+
| Stability | [development]: traces |
7+
| | [alpha]: metrics |
78
| Distributions | [contrib] |
89
| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Fgithub%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fgithub) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Fgithub%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Fgithub) |
910
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@adrielp](https://www.github.com/adrielp), [@andrzej-stencel](https://www.github.com/andrzej-stencel), [@crobert-1](https://www.github.com/crobert-1), [@TylerHelmuth](https://www.github.com/TylerHelmuth) |
1011

12+
[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
1113
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
1214
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
1315
<!-- end autogenerated section -->
1416

15-
The GitHub receiver receives data from [GitHub](https://github.com). As a
16-
starting point it scrapes metrics from repositories but will be extended to
17-
include traces and logs.
17+
The GitHub receiver receives data from [GitHub](https://github.com).
1818

1919
The current default set of metrics can be found in
2020
[documentation.md](./documentation.md).
@@ -26,13 +26,13 @@ engineering practices.
2626
[doracap]: https://dora.dev/capabilities/
2727
[dorafour]: https://dora.dev/guides/dora-metrics-four-keys/
2828

29-
## Getting Started
29+
## Metrics - Getting Started
3030

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

3333
> Note: Generally speaking, if the vendor allows for anonymous API calls, then you
3434
> won't have to configure any authentication, but you may only see public repositories
35-
> and organizations. You may run into significantly more rate limiting.
35+
> and organizations. You may also run into significantly more rate limiting.
3636
3737
```yaml
3838
github:
@@ -92,3 +92,56 @@ For additional context on GitHub scraper limitations and inner workings please
9292
see the [Scraping README][ghsread].
9393
9494
[ghsread]: internal/scraper/githubscraper/README.md#github-limitations
95+
96+
## Traces - Getting Started
97+
98+
Workflow tracing support is actively being added to the GitHub receiver.
99+
This is accomplished through the processing of GitHub Actions webhook
100+
events for workflows and jobs. The [`workflow_job`][wjob] and
101+
[`workflow_run`][wrun] event payloads are then constructed into `trace`
102+
telemetry.
103+
104+
Each GitHub Action workflow or job, along with its steps, are converted
105+
into trace spans, allowing the observation of workflow execution times,
106+
success, and failure rates.
107+
108+
### Configuration
109+
110+
**IMPORTANT: At this time the tracing portion of this receiver only serves a health check endpoint.**
111+
112+
The WebHook configuration exposes the following settings:
113+
114+
* `endpoint`: (default = `localhost:8080`) - The address and port to bind the WebHook to.
115+
* `path`: (default = `/events`) - The path for Action events to be sent to.
116+
* `health_path`: (default = `/health`) - The path for health checks.
117+
* `secret`: (optional) - The secret used to [validates the payload][valid].
118+
* `required_header`: (optional) - The required header key and value for incoming requests.
119+
120+
The WebHook configuration block also accepts all the [confighttp][cfghttp]
121+
settings.
122+
123+
An example configuration is as follows:
124+
125+
```yaml
126+
receivers:
127+
github:
128+
scrapers:
129+
... <scraper configuration>: # Scraper configurations are required until Tracing functionality is complete.
130+
webhook:
131+
endpoint: localhost:19418
132+
path: /events
133+
health_path: /health
134+
secret: ${env:SECRET_STRING_VAR}
135+
required_header:
136+
key: "X-GitHub-Event"
137+
value: "action"
138+
```
139+
140+
For tracing, all configuration is set under the `webhook` key. The full set
141+
of exposed configuration values can be found in [`config.go`][config.go].
142+
143+
[wjob]: https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_job
144+
[wrun]: https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_run
145+
[valid]: https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
146+
[config.go] ./config.go
147+
[cfghttp]: https://pkg.go.dev/go.opentelemetry.io/collector/config/confighttp#ServerConfig

Diff for: receiver/githubreceiver/config.go

100644100755
+48-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ package githubreceiver // import "github.com/open-telemetry/opentelemetry-collec
66
import (
77
"errors"
88
"fmt"
9+
"time"
910

1011
"go.opentelemetry.io/collector/component"
12+
"go.opentelemetry.io/collector/config/confighttp"
1113
"go.opentelemetry.io/collector/confmap"
1214
"go.opentelemetry.io/collector/receiver/scraperhelper"
15+
"go.uber.org/multierr"
1316

1417
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/githubreceiver/internal"
1518
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/githubreceiver/internal/metadata"
@@ -24,19 +27,62 @@ type Config struct {
2427
scraperhelper.ControllerConfig `mapstructure:",squash"`
2528
Scrapers map[string]internal.Config `mapstructure:"scrapers"`
2629
metadata.MetricsBuilderConfig `mapstructure:",squash"`
30+
WebHook WebHook `mapstructure:"webhook"`
31+
}
32+
33+
type WebHook struct {
34+
confighttp.ServerConfig `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
35+
Path string `mapstructure:"path"` // path for data collection. Default is /events
36+
HealthPath string `mapstructure:"health_path"` // path for health check api. Default is /health_check
37+
RequiredHeader RequiredHeader `mapstructure:"required_header"` // optional setting to set a required header for all requests to have
38+
Secret string `mapstructure:"secret"` // secret for webhook
39+
}
40+
41+
type RequiredHeader struct {
42+
Key string `mapstructure:"key"`
43+
Value string `mapstructure:"value"`
2744
}
2845

2946
var (
3047
_ component.Config = (*Config)(nil)
3148
_ confmap.Unmarshaler = (*Config)(nil)
49+
50+
errMissingEndpointFromConfig = errors.New("missing receiver server endpoint from config")
51+
errReadTimeoutExceedsMaxValue = errors.New("the duration specified for read_timeout exceeds the maximum allowed value of 10s")
52+
errWriteTimeoutExceedsMaxValue = errors.New("the duration specified for write_timeout exceeds the maximum allowed value of 10s")
53+
errRequiredHeader = errors.New("both key and value are required to assign a required_header")
54+
errRequireOneScraper = errors.New("must specify at least one scraper")
3255
)
3356

3457
// Validate the configuration passed through the OTEL config.yaml
3558
func (cfg *Config) Validate() error {
59+
var errs error
60+
61+
// For now, scrapers are required to be defined in the config. As tracing
62+
// and other signals are added, this requirement will change.
3663
if len(cfg.Scrapers) == 0 {
37-
return errors.New("must specify at least one scraper")
64+
errs = multierr.Append(errs, errRequireOneScraper)
3865
}
39-
return nil
66+
67+
maxReadWriteTimeout, _ := time.ParseDuration("10s")
68+
69+
if cfg.WebHook.ServerConfig.Endpoint == "" {
70+
errs = multierr.Append(errs, errMissingEndpointFromConfig)
71+
}
72+
73+
if cfg.WebHook.ServerConfig.ReadTimeout > maxReadWriteTimeout {
74+
errs = multierr.Append(errs, errReadTimeoutExceedsMaxValue)
75+
}
76+
77+
if cfg.WebHook.ServerConfig.WriteTimeout > maxReadWriteTimeout {
78+
errs = multierr.Append(errs, errWriteTimeoutExceedsMaxValue)
79+
}
80+
81+
if (cfg.WebHook.RequiredHeader.Key != "" && cfg.WebHook.RequiredHeader.Value == "") || (cfg.WebHook.RequiredHeader.Value != "" && cfg.WebHook.RequiredHeader.Key == "") {
82+
errs = multierr.Append(errs, errRequiredHeader)
83+
}
84+
85+
return errs
4086
}
4187

4288
// Unmarshal a config.Parser into the config struct.

Diff for: receiver/githubreceiver/config_test.go

+33-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/stretchr/testify/assert"
1212
"github.com/stretchr/testify/require"
1313
"go.opentelemetry.io/collector/component"
14+
"go.opentelemetry.io/collector/config/confighttp"
1415
"go.opentelemetry.io/collector/confmap"
1516
"go.opentelemetry.io/collector/otelcol/otelcoltest"
1617
"go.opentelemetry.io/collector/receiver/scraperhelper"
@@ -26,6 +27,7 @@ func TestLoadConfig(t *testing.T) {
2627

2728
factory := NewFactory()
2829
factories.Receivers[metadata.Type] = factory
30+
2931
// https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/33594
3032
// nolint:staticcheck
3133
cfg, err := otelcoltest.LoadConfigAndValidate(filepath.Join("testdata", "config.yaml"), factories)
@@ -36,12 +38,27 @@ func TestLoadConfig(t *testing.T) {
3638
assert.Len(t, cfg.Receivers, 2)
3739

3840
r0 := cfg.Receivers[component.NewID(metadata.Type)]
39-
defaultConfigGitHubScraper := factory.CreateDefaultConfig()
40-
defaultConfigGitHubScraper.(*Config).Scrapers = map[string]internal.Config{
41+
defaultConfigGitHubReceiver := factory.CreateDefaultConfig()
42+
43+
defaultConfigGitHubReceiver.(*Config).Scrapers = map[string]internal.Config{
4144
metadata.Type.String(): (&githubscraper.Factory{}).CreateDefaultConfig(),
4245
}
4346

44-
assert.Equal(t, defaultConfigGitHubScraper, r0)
47+
defaultConfigGitHubReceiver.(*Config).WebHook = WebHook{
48+
ServerConfig: confighttp.ServerConfig{
49+
Endpoint: "localhost:8080",
50+
ReadTimeout: 500 * time.Millisecond,
51+
WriteTimeout: 500 * time.Millisecond,
52+
},
53+
Path: "some/path",
54+
HealthPath: "health/path",
55+
RequiredHeader: RequiredHeader{
56+
Key: "key-present",
57+
Value: "value-present",
58+
},
59+
}
60+
61+
assert.Equal(t, defaultConfigGitHubReceiver, r0)
4562

4663
r1 := cfg.Receivers[component.NewIDWithName(metadata.Type, "customname")].(*Config)
4764
expectedConfig := &Config{
@@ -52,6 +69,19 @@ func TestLoadConfig(t *testing.T) {
5269
Scrapers: map[string]internal.Config{
5370
metadata.Type.String(): (&githubscraper.Factory{}).CreateDefaultConfig(),
5471
},
72+
WebHook: WebHook{
73+
ServerConfig: confighttp.ServerConfig{
74+
Endpoint: "localhost:8080",
75+
ReadTimeout: 500 * time.Millisecond,
76+
WriteTimeout: 500 * time.Millisecond,
77+
},
78+
Path: "some/path",
79+
HealthPath: "health/path",
80+
RequiredHeader: RequiredHeader{
81+
Key: "key-present",
82+
Value: "value-present",
83+
},
84+
},
5585
}
5686

5787
assert.Equal(t, expectedConfig, r1)

Diff for: receiver/githubreceiver/factory.go

+35-6
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import (
77
"context"
88
"errors"
99
"fmt"
10+
"time"
1011

1112
"go.opentelemetry.io/collector/component"
13+
"go.opentelemetry.io/collector/config/confighttp"
1214
"go.opentelemetry.io/collector/consumer"
1315
"go.opentelemetry.io/collector/receiver"
1416
"go.opentelemetry.io/collector/receiver/scraperhelper"
@@ -21,6 +23,14 @@ import (
2123

2224
// This file implements a factory for the github receiver
2325

26+
const (
27+
defaultReadTimeout = 500 * time.Millisecond
28+
defaultWriteTimeout = 500 * time.Millisecond
29+
defaultPath = "/events"
30+
defaultHealthPath = "/health"
31+
defaultEndpoint = "localhost:8080"
32+
)
33+
2434
var (
2535
scraperFactories = map[string]internal.ScraperFactory{
2636
metadata.Type.String(): &githubscraper.Factory{},
@@ -35,6 +45,7 @@ func NewFactory() receiver.Factory {
3545
metadata.Type,
3646
createDefaultConfig,
3747
receiver.WithMetrics(createMetricsReceiver, metadata.MetricsStability),
48+
receiver.WithTraces(createTracesReceiver, metadata.TracesStability),
3849
)
3950
}
4051

@@ -51,12 +62,15 @@ func getScraperFactory(key string) (internal.ScraperFactory, bool) {
5162
func createDefaultConfig() component.Config {
5263
return &Config{
5364
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
54-
// TODO: metrics builder configuration may need to be in each sub scraper,
55-
// TODO: for right now setting here because the metrics in this receiver will apply to all
56-
// TODO: scrapers defined as a common set of github
57-
// TODO: aqp completely remove these comments if the metrics build config
58-
// needs to be defined in each scraper
59-
// MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(),
65+
WebHook: WebHook{
66+
ServerConfig: confighttp.ServerConfig{
67+
Endpoint: defaultEndpoint,
68+
ReadTimeout: defaultReadTimeout,
69+
WriteTimeout: defaultWriteTimeout,
70+
},
71+
Path: defaultPath,
72+
HealthPath: defaultHealthPath,
73+
},
6074
}
6175
}
6276

@@ -87,6 +101,21 @@ func createMetricsReceiver(
87101
)
88102
}
89103

104+
func createTracesReceiver(
105+
_ context.Context,
106+
params receiver.Settings,
107+
cfg component.Config,
108+
consumer consumer.Traces,
109+
) (receiver.Traces, error) {
110+
// check that the configuration is valid
111+
conf, ok := cfg.(*Config)
112+
if !ok {
113+
return nil, errConfigNotValid
114+
}
115+
116+
return newTracesReceiver(params, conf, consumer)
117+
}
118+
90119
func createAddScraperOpts(
91120
ctx context.Context,
92121
params receiver.Settings,

Diff for: receiver/githubreceiver/factory_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ func TestCreateDefaultConfig(t *testing.T) {
3030

3131
func TestCreateReceiver(t *testing.T) {
3232
factory := NewFactory()
33-
cfg := factory.CreateDefaultConfig()
33+
cfg := factory.CreateDefaultConfig().(*Config)
3434

3535
tReceiver, err := factory.CreateTraces(context.Background(), creationSet, cfg, consumertest.NewNop())
36-
assert.Equal(t, err, pipeline.ErrSignalNotSupported)
37-
assert.Nil(t, tReceiver)
36+
assert.NoError(t, err)
37+
assert.NotNil(t, tReceiver)
3838

3939
mReceiver, err := factory.CreateMetrics(context.Background(), creationSet, cfg, consumertest.NewNop())
4040
assert.NoError(t, err)

Diff for: receiver/githubreceiver/generated_component_test.go

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)