Skip to content

Commit

Permalink
feat: refactor from githubactionsevent to githubactions
Browse files Browse the repository at this point in the history
  • Loading branch information
krzko committed Nov 23, 2023
1 parent 0acbb4e commit 8c8d9c8
Show file tree
Hide file tree
Showing 47 changed files with 9,613 additions and 0 deletions.
2 changes: 2 additions & 0 deletions receiver/githubactionsreceiver/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include ../../Makefile.Common

79 changes: 79 additions & 0 deletions receiver/githubactionsreceiver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# GitHub Actions Receiver

<!-- status autogenerated section -->
| Status | |
| ------------- |-----------|
| Stability | [alpha]: traces |
| Distributions | [] |
| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Fgithubactionsevent%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fgithubactionsevent) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Fgithubactionsevent%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Fgithubactionsevent) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@krzko](https://www.github.com/krzko), [@rich-bain](https://www.github.com/rich-bain) |

[alpha]: https://github.com/open-telemetry/opentelemetry-collector#alpha
<!-- end autogenerated section -->

The GitHub Actions Receiver processes GitHub Actions webhook events to observe workflows and jobs. It handles [`workflow_job`](https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_job) and [`workflow_run`](https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_run) event payloads, transforming them into `trace` telemetry.

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.

If a secret is configured (recommended), it [validates the payload](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) ensuring data integrity before processing.

## Configuration

The following settings are required:

* `endpoint` (no default): The endpoint where you may point your webhook to emit events to

The following settings are optional:

* `path` (default: '/events'): Path where the receiver instance will accept events
* `secret`: GitHub webhook hash signature

Example:
```yaml
receivers:
githubactions:
endpoint: localhost:19418
path: /events
secret: It's a Secret to Everybody
```
The full list of settings exposed for this receiver are documented [here](./config.go) with a detailed sample configuration [here](./testdata/config.yaml)
## GitHub repository webhooks
Webhooks provide a way for notifications to be delivered to an external web server whenever certain events occur on GitHub.
### Delivery headers
HTTP POST payloads that are delivered to your webhook's configured URL endpoint will contain several special headers:
* `X-Hub-Signature`: This header is sent if the webhook is configured with a `secret`. This is the HMAC hex digest of the request body, and is generated using the SHA-1 hash function and the `secret` as the HMAC `key`. `X-Hub-Signature` is provided for compatibility with existing integrations. It's recommended that you use the more secure `X-Hub-Signature-256` instead.
* `X-Hub-Signature-256`: This header is sent if the webhook is configured with a `secret` (recommended). This is the HMAC hex digest of the request body, and is generated using the SHA-256 hash function and the `secret` as the HMAC `key`.
* `User-Agent`: This header will always have the prefix `GitHub-Hookshot/`.

### Creating webhooks

You can create webhooks to subscribe to specific events that occur on GitHub.

#### Creating a respositoty webhook

You can create a webhook to subscribe to events that occur in a specific repository. You must be a repository owner or have admin access in the repository to create webhooks in that repository.

1. On **github.com**, navigate to the main page of the repository.
2. Under your repository name, click **Settings**. If you cannot see the "Settings" tab, select the dropdown menu, then click Settings.
3. In the left sidebar, click **Webhooks**.
4. Click Add **webhook**.
5. Under "Payload URL", type the URL where you'd like to receive payloads.
6. Select the **Content type** drop-down menu, and click a data format to receive the webhook payload in.
* Select `application/json`, which will deliver the JSON payload directly as the body of the `POST` request.
7. Optionally (recomended), under "Secret", type a string to use as a secret key. You should choose a random string of text with high entropy. You can use the webhook secret to limit incoming requests to only those originating from GitHub.
8. Under "Which events would you like to trigger this webhook?"
* Select **Let me select individual events**.
* Uncheck **Pushes**, which is selected by default.
* Select **Workflow jobs**.
* Select **Workflow runs**.
9. To make the webhook active immediately after adding the configuration, select **Active**.
10. Click **Add webhook**.

You can use the GitHub web interface or the REST API to create a repository webhook. For more information about using the REST API to create a repository webhook, see [Repository Webhooks](https://docs.github.com/en/rest/webhooks/repos?apiVersion=2022-11-28#create-a-repository-webhook).

Alternatively you can use a configuration management tool such as [`terraform`](https://www.terraform.io/) with the [`github_repository_webhook`](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/repository_webhook.html) resource, to configure webhooks.
38 changes: 38 additions & 0 deletions receiver/githubactionsreceiver/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

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

import (
"errors"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"

"go.uber.org/multierr"
)

var errMissingEndpointFromConfig = errors.New("missing receiver server endpoint from config")

// Config defines configuration for GitHub Actions receiver
type Config struct {
confighttp.HTTPServerSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
Path string `mapstructure:"path"` // path for data collection. Default is <host>:<port>/events
Secret string `mapstructure:"secret"` // github webhook hash signature. Default is empty
CustomServiceName string `mapstructure:"custom_service_name"` // custom service name. Default is empty
ServiceNamePrefix string `mapstructure:"service_name_prefix"` // service name prefix. Default is empty
ServiceNameSuffix string `mapstructure:"service_name_suffix"` // service name suffix. Default is empty
}

var _ component.Config = (*Config)(nil)

// Validate checks the receiver configuration is valid
func (cfg *Config) Validate() error {
var errs error

if cfg.HTTPServerSettings.Endpoint == "" {
errs = multierr.Append(errs, errMissingEndpointFromConfig)
}

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

package githubactionsreceiver

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/confmap/confmaptest"

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

// only one validate check so far
func TestValidateConfig(t *testing.T) {
t.Parallel()

tests := []struct {
desc string
expect error
conf Config
}{
{
desc: "Missing valid endpoint",
expect: errMissingEndpointFromConfig,
conf: Config{
HTTPServerSettings: confighttp.HTTPServerSettings{
Endpoint: "",
},
},
},
{
desc: "Valid Secret",
expect: nil,
conf: Config{
HTTPServerSettings: confighttp.HTTPServerSettings{
Endpoint: "localhost:8080",
},
Secret: "mysecret",
},
},
}

for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
err := test.conf.Validate()
if test.expect == nil {
require.NoError(t, err)
require.Equal(t, "mysecret", test.conf.Secret)
} else {
require.Error(t, err)
require.Contains(t, err.Error(), test.expect.Error())
}
})
}
}

func TestLoadConfig(t *testing.T) {
t.Parallel()

cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)
// LoadConf includes the TypeStr which NewFactory does not set
id := component.NewIDWithName(metadata.Type, "valid_config")
cmNoStr, err := cm.Sub(id.String())
require.NoError(t, err)

expect := &Config{
HTTPServerSettings: confighttp.HTTPServerSettings{
Endpoint: "localhost:8080",
},
Path: "/events",
Secret: "mysecret",
}

// create expected config
factory := NewFactory()
conf := factory.CreateDefaultConfig()
require.NoError(t, component.UnmarshalConfig(cmNoStr, conf))
require.NoError(t, component.ValidateConfig(conf))

require.Equal(t, expect, conf)
}
6 changes: 6 additions & 0 deletions receiver/githubactionsreceiver/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 githubactionsreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/githubactionsreceiver"
53 changes: 53 additions & 0 deletions receiver/githubactionsreceiver/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

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

import (
"context"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"

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

// This file implements factory for GitHub Actions receiver.

const (
defaultBindEndpoint = "0.0.0.0:19418"
defaultPath = "/ghaevents"
)

// NewFactory creates a new GitHub Actions receiver factory
func NewFactory() receiver.Factory {
return receiver.NewFactory(
metadata.Type,
createDefaultConfig,
receiver.WithTraces(createTracesReceiver, metadata.TracesStability),
)
}

// createDefaultConfig creates the default configuration for GitHub Actions receiver.
func createDefaultConfig() component.Config {
return &Config{
HTTPServerSettings: confighttp.HTTPServerSettings{
Endpoint: defaultBindEndpoint,
},
Path: defaultPath,
Secret: "",
}
}

// createTracesReceiver creates a trace receiver based on provided config.
func createTracesReceiver(
_ context.Context,
set receiver.CreateSettings,
cfg component.Config,
nextConsumer consumer.Traces,
) (receiver.Traces, error) {
rCfg := cfg.(*Config)
return newTracesReceiver(set, rCfg, nextConsumer)
}
71 changes: 71 additions & 0 deletions receiver/githubactionsreceiver/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package githubactionsreceiver

import (
"context"
"testing"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver/receivertest"
)

func TestFactoryCreate(t *testing.T) {
factory := NewFactory()
require.EqualValues(t, "githubactions", factory.Type())
}

func TestDefaultConfig(t *testing.T) {
cfg := createDefaultConfig()
require.NotNil(t, cfg, "Failed to create default configuration")
}

func TestCreateTracesReceiver(t *testing.T) {
tests := []struct {
desc string
run func(t *testing.T)
}{
{
desc: "Defaults with valid inputs",
run: func(t *testing.T) {
t.Parallel()

cfg := createDefaultConfig().(*Config)
cfg.Endpoint = "localhost:8080"
require.NoError(t, cfg.Validate(), "error validating default config")

_, err := createTracesReceiver(
context.Background(),
receivertest.NewNopCreateSettings(),
cfg,
consumertest.NewNop(),
)
require.NoError(t, err, "failed to create trace receiver")
},
},
{
desc: "Missing consumer",
run: func(t *testing.T) {
t.Parallel()

cfg := createDefaultConfig().(*Config)
cfg.Endpoint = "localhost:8080"
require.NoError(t, cfg.Validate(), "error validating default config")

_, err := createTracesReceiver(
context.Background(),
receivertest.NewNopCreateSettings(),
cfg,
nil,
)
require.Error(t, err, "Succeeded in creating a receiver without a consumer")
},
},
}

for _, test := range tests {
t.Run(test.desc, test.run)
}
}
Loading

0 comments on commit 8c8d9c8

Please sign in to comment.