1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,118 @@
# Webhooks Input Plugin
This is a Telegraf service plugin that start a http server and register
multiple webhook listeners.
```sh
telegraf config -input-filter webhooks -output-filter influxdb > config.conf.new
```
Change the config file to point to the InfluxDB server you are using and adjust
the settings to match your environment. Once that is complete:
```sh
cp config.conf.new /etc/telegraf/telegraf.conf
sudo service telegraf start
```
## Service Input <!-- @/docs/includes/service_input.md -->
This plugin is a service input. Normal plugins gather metrics determined by the
interval setting. Service plugins start a service to listen and wait for
metrics or events to occur. Service plugins have two key differences from
normal plugins:
1. The global or plugin specific `interval` setting may not apply
2. The CLI options of `--test`, `--test-wait`, and `--once` may not produce
output for this plugin
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# A Webhooks Event collector
[[inputs.webhooks]]
## Address and port to host Webhook listener on
service_address = ":1619"
## Maximum duration before timing out read of the request
# read_timeout = "10s"
## Maximum duration before timing out write of the response
# write_timeout = "10s"
[inputs.webhooks.filestack]
path = "/filestack"
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.github]
path = "/github"
# secret = ""
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.mandrill]
path = "/mandrill"
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.rollbar]
path = "/rollbar"
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.papertrail]
path = "/papertrail"
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.particle]
path = "/particle"
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.artifactory]
path = "/artifactory"
```
## Available webhooks
- [Filestack](filestack/)
- [Github](github/)
- [Mandrill](mandrill/)
- [Rollbar](rollbar/)
- [Papertrail](papertrail/)
- [Particle](particle/)
- [Artifactory](artifactory/)
## Adding new webhooks plugin
1. Add your webhook plugin inside the `webhooks` folder
1. Your plugin must implement the `Webhook` interface
1. Import your plugin in the `webhooks.go` file and add it to the `Webhooks` struct
Both [Github](github/) and [Rollbar](rollbar/) are good example to follow.
## Metrics
## Example Output

View file

@ -0,0 +1,528 @@
# Artifactory Webhook
You need to configure the organization's artifactory instance(s) as detailed
via the artifactory [webhook documentation][webhook docs]. Multiple webhooks may
need be needed to configure different domains.
You can also add a secret that will be used by telegraf to verify the
authenticity of the requests.
[webhook docs]: https://www.jfrog.com/confluence/display/JFROG/Webhooks
## Events
The different events type can be found found in the webhook documentation:
<https://www.jfrog.com/confluence/display/JFROG/Webhooks>.
Events are identified by their `domain` and `event`.
The following sections break down each event by domain.
### Artifact Domain
#### Artifact Deployed Event
The Webhook is triggered when an artifact is deployed to a repository.
**Tags:**
* 'domain' string
* 'event_type' string
* 'repo' string
* 'path' string
* 'name' string
**Fields:**
* 'size' int
* 'sha256' string
#### Artifact Deleted Event
The Webhook is triggered when an artifact is deleted from a repository.
**Tags:**
* 'domain' string
* 'event_type' string
* 'repo' string
* 'path' string
* 'name' string
**Fields:**
* 'size' int
* 'sha256' string
#### Artifact Moved Event
The Webhook is triggered when an artifact is moved from a repository.
**Tags:**
* 'domain' string
* 'event_type' string
* 'repo' string
* 'path' string
* 'name' string
**Fields:**
* 'size' int
* 'source_path' string
* 'target_path' string
#### Artifact Copied Event
The Webhook is triggered when an artifact is copied from a repository.
**Tags:**
* 'domain' string
* 'event_type' string
* 'repo' string
* 'path' string
* 'name' string
**Fields:**
* 'size' int
* 'source_path' string
* 'target_path' string
### Artifact Properties Domain
#### Properties Added Event
The Webhook is triggered when a property is added to an artifact/folder
in a repository, or the repository itself.
**Tags:**
* 'domain' string
* 'event_type' string
* 'repo' string
* 'path' string
* 'name' string
**Fields**
* 'property_key' string
* 'property_values' string (joined comma separated list)
#### Properties Deleted Event
The Webhook is triggered when a property is deleted from an artifact/folder in a
repository, or the repository itself.
**Tags:**
* 'domain' string
* 'event_type' string
* 'repo' string
* 'path' string
* 'name' string
**Fields:**
* 'property_key' string
* 'property_values' string (joined comma separated list)
### Docker Domain
#### Docker Pushed Event
The Webhook is triggered when a new tag of a Docker image is pushed to a Docker
repository.
**Tags:**
* 'domain' string
* 'event_type' string
* 'repo' string
* 'path' string
* 'name' string
* 'image_name' string
**Fields:**
* 'size' string
* 'sha256' string
* 'tag' string
* 'platforms' []object
* 'architecture' string
* 'os' string
#### Docker Deleted Event
The Webhook is triggered when a tag of a Docker image is deleted from a Docker
repository.
**Tags:**
* 'domain' string
* 'event_type' string
* 'repo' string
* 'path' string
* 'name' string
* 'image_name' string
**Fields:**
* 'size' string
* 'sha256' string
* 'tag' string
* 'platforms' []object
* 'architecture' string
* 'os' string
#### Docker Promoted Event
The Webhook is triggered when a tag of a Docker image is promoted.
**Tags:**
* 'domain' string
* 'event_type' string
* 'repo' string
* 'path' string
* 'name' string
* 'image_name' string
**Fields:**
* 'size' string
* 'sha256' string
* 'tag' string
* 'platforms' []object
* 'architecture' string
* 'os' string
### Build Domain
#### Build Uploaded Event
The Webhook is triggered when a new build is uploaded.
**Tags:**
* 'domain' string
* 'event_type' string
**Fields:**
* 'build_name' string
* 'build_number' string
* 'build_started' string
#### Build Deleted Event
The Webhook is triggered when a build is deleted.
**Tags:**
* 'domain' string
* 'event_type' string
**Fields:**
* 'build_name' string
* 'build_number' string
* 'build_started' string
#### Build Promoted Event
The Webhook is triggered when a build is promoted.
**Tags:**
* 'domain' string
* 'event_type' string
**Fields:**
* 'build_name' string
* 'build_number' string
* 'build_started' string
### Release Bundle Domain
#### Release Bundle Created Event
The Webhook is triggered when a Release Bundle is created.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
**Fields:**
* 'release_bundle_name' string
* 'release_bundle_size' string
* 'release_bundle_version' string
* 'jpd_origin' string
#### Release Bundle Signed Event
The Webhook is triggered when a Release Bundle is signed.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
**Fields:**
* 'release_bundle_name' string
* 'release_bundle_size' string
* 'release_bundle_version' string
* 'jpd_origin' string
#### Release Bundle Deleted Event
The Webhook is triggered when a Release Bundle is deleted.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_size' string
* 'release_bundle_version' string
* 'jpd_origin' string
### Release Bundle Distribution Domain
#### Release Bundle Distribution Started Event
The Webhook is triggered when Release Bundle distribution has started
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_size' string
* 'release_bundle_version' string
* 'status_message' string
* 'transaction_id' string
* 'edge_node_info_list' []object
* 'edge_node_address' string
* 'edge_node_name' string
* 'jpd_origin' string
#### Release Bundle Distribution Completed Event
The Webhook is triggered when Release Bundle distribution has completed.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_size' string
* 'release_bundle_version' string
* 'status_message' string
* 'transaction_id' string
* 'edge_node_info_list' []object
* 'edge_node_address' string
* 'edge_node_name' string
* 'jpd_origin' string
#### Release Bundle Distribution Aborted Event
The Webhook is triggered when Release Bundle distribution has been aborted.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_size' string
* 'release_bundle_version' string
* 'status_message' string
* 'transaction_id' string
* 'edge_node_info_list' []object
* 'edge_node_address' string
* 'edge_node_name' string
* 'jpd_origin' string
#### Release Bundle Distribution Failed Event
The Webhook is triggered when Release Bundle distribution has failed.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_size' string
* 'release_bundle_version' string
* 'status_message' string
* 'transaction_id' string
* 'edge_node_info_list' []object
* 'edge_node_address' string
* 'edge_node_name' string
* 'jpd_origin' string
### Release Bundle Version Domain
#### Release Bundle Version Deletion Started EVent
The Webhook is triggered when a Release Bundle version deletion has started on
one or more Edge nodes.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_size' string
* 'release_bundle_version' string
* 'status_message' string
* 'transaction_id' string
* 'edge_node_info_list' []object
* 'edge_node_address' string
* 'edge_node_name' string
* 'jpd_origin' string
#### Release Bundle Version Deletion Completed Event
The Webhook is triggered when a Release Bundle version deletion has completed
from one or more Edge nodes.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_size' string
* 'release_bundle_version' string
* 'status_message' string
* 'transaction_id' string
* 'edge_node_info_list' []object
* 'edge_node_address' string
* 'edge_node_name' string
* 'jpd_origin' string
#### Release Bundle Version Deletion Failed Event
The Webhook is triggered when a Release Bundle version deletion has failed on
one or more Edge nodes.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_size' string
* 'release_bundle_version' string
* 'status_message' string
* 'transaction_id' string
* 'edge_node_info_list' []object
* 'edge_node_address' string
* 'edge_node_name' string
* 'jpd_origin' string
### Release Bundle Destination Domain
#### Release Bundle Received Event
The Webhook is triggered when a Release Bundle was received on an Edge Node.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_version' string
* 'status_message' string
* 'jpd_origin' string
### Release Bundle Delete Started Event
The Webhook is triggered when a Release Bundle deletion from an Edge Node
completed.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_version' string
* 'status_message' string
* 'jpd_origin' string
#### Release Bundle Delete Completed Event
The Webhook is triggered when a Release Bundle deletion from an Edge Node
completed.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_version' string
* 'status_message' string
* 'jpd_origin' string
#### Release Bundle Delete Failed Event
The Webhook is triggered when a Release Bundle deletion from an Edge Node fails.
**Tags:**
* 'domain' string
* 'event_type' string
* 'destination' string
* 'release_bundle_name' string
**Fields:**
* 'release_bundle_version' string
* 'status_message' string
* 'jpd_origin' string

View file

@ -0,0 +1,122 @@
package artifactory
import (
"crypto/hmac"
"crypto/sha1" //nolint:gosec // G505: Blocklisted import crypto/sha1: weak cryptographic primitive - sha1 hash is what is desired in this case
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/gorilla/mux"
"github.com/influxdata/telegraf"
)
type Webhook struct {
Path string
Secret string
acc telegraf.Accumulator
log telegraf.Logger
}
// Register registers the webhook with the provided router
func (awh *Webhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) {
router.HandleFunc(awh.Path, awh.eventHandler).Methods("POST")
awh.log = log
awh.log.Infof("Started webhooks_artifactory on %s", awh.Path)
awh.acc = acc
}
func (awh *Webhook) eventHandler(rw http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
data, err := io.ReadAll(r.Body)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
return
}
if awh.Secret != "" && !checkSignature(awh.Secret, data, r.Header.Get("x-jfrog-event-auth")) {
awh.log.Error("Failed to check the artifactory webhook auth signature")
rw.WriteHeader(http.StatusBadRequest)
return
}
bodyFields := make(map[string]interface{})
err = json.Unmarshal(data, &bodyFields)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
}
et := fmt.Sprintf("%v", bodyFields["event_type"])
ed := fmt.Sprintf("%v", bodyFields["domain"])
ne, err := awh.newEvent(data, et, ed)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
}
if ne != nil {
nm := ne.newMetric()
awh.acc.AddFields("artifactory_webhooks", nm.Fields(), nm.Tags(), nm.Time())
}
rw.WriteHeader(http.StatusOK)
}
type newEventError struct {
s string
}
func (e *newEventError) Error() string {
return e.s
}
func (awh *Webhook) newEvent(data []byte, et, ed string) (event, error) {
awh.log.Debugf("New %v domain %v event received", ed, et)
switch ed {
case "artifact":
if et == "deployed" || et == "deleted" {
return generateEvent(data, &artifactDeploymentOrDeletedEvent{})
}
if et == "moved" || et == "copied" {
return generateEvent(data, &artifactMovedOrCopiedEvent{})
}
return nil, &newEventError{"Not a recognized event type"}
case "artifact_property":
return generateEvent(data, &artifactPropertiesEvent{})
case "docker":
return generateEvent(data, &dockerEvent{})
case "build":
return generateEvent(data, &buildEvent{})
case "release_bundle":
return generateEvent(data, &releaseBundleEvent{})
case "distribution":
return generateEvent(data, &distributionEvent{})
case "destination":
return generateEvent(data, &destinationEvent{})
}
return nil, &newEventError{"Not a recognized event type"}
}
func generateEvent(data []byte, event event) (event, error) {
err := json.Unmarshal(data, event)
if err != nil {
return nil, err
}
return event, nil
}
func checkSignature(secret string, data []byte, signature string) bool {
return hmac.Equal([]byte(signature), []byte(generateSignature(secret, data)))
}
func generateSignature(secret string, data []byte) string {
mac := hmac.New(sha1.New, []byte(secret))
if _, err := mac.Write(data); err != nil {
return err.Error()
}
result := mac.Sum(nil)
return "sha1=" + hex.EncodeToString(result)
}

View file

@ -0,0 +1,439 @@
package artifactory
func unsupportedEventJSON() string {
return `
{
"domain": "not_supported",
"event_type": "not_supported",
"data": {
"name": "sample.txt",
"path": "sample_dir/sample.txt",
"repo_key": "sample_repo",
"sha256": "sample_checksum",
"size": 0
}
}`
}
func artifactDeployedEventJSON() string {
return `
{
"domain": "artifact",
"event_type": "deployed",
"data": {
"name": "sample.txt",
"path": "sample_dir/sample.txt",
"repo_key": "sample_repo",
"sha256": "sample_checksum",
"size": 0
}
}`
}
func artifactDeletedEventJSON() string {
return `
{
"domain": "artifact",
"event_type": "deleted",
"data": {
"name": "sample.txt",
"path": "sample_dir/sample.txt",
"repo_key": "sample_repo",
"sha256": "sample_checksum",
"size": 0
}
}`
}
func artifactMovedEventJSON() string {
return `
{
"domain": "artifact",
"event_type": "moved",
"data": {
"name": "sample.txt",
"path": "sample_dir/sample.txt",
"repo_key": "sample_repo",
"sha256": "sample_checksum",
"size": 0,
"source_repo_path": "sample_repo",
"target_repo_path": "sample_target_repo"
}
}`
}
func artifactCopiedEventJSON() string {
return `
{
"domain": "artifact",
"event_type": "copied",
"data": {
"name": "sample.txt",
"path": "sample_dir/sample.txt",
"repo_key": "sample_repo",
"sha256": "sample_checksum",
"size": 0,
"source_repo_path": "sample_repo",
"target_repo_path": "sample_target_repo"
}
}`
}
func artifactPropertiesAddedEventJSON() string {
return `
{
"domain": "artifact_property",
"event_type": "added",
"data": {
"name": "sample.txt",
"path": "sample_dir/sample.txt",
"property_key": "sample_key",
"property_values": [
"sample_value1"
],
"repo_key": "sample_repo",
"sha256": "sample_checksum",
"size": 0
}
}`
}
func artifactPropertiesDeletedEventJSON() string {
return `
{
"domain": "artifact_property",
"event_type": "deleted",
"data": {
"name": "sample.txt",
"path": "sample_dir/sample.txt",
"property_key": "sample_key",
"property_values": [
"sample_value1"
],
"repo_key": "sample_repo",
"sha256": "sample_checksum",
"size": 0
}
}`
}
func dockerPushedEventJSON() string {
return `
{
"domain": "docker",
"event_type": "pushed",
"data": {
"image_name": "sample_arch",
"name": "sample.txt",
"path": "sample_dir/sample.txt",
"platforms": [
{
"architecture": "sample_os",
"os": "sample_tag"
}
],
"repo_key": "sample_repo",
"sha256": "sample_checksum",
"size": 0,
"tag": "sample_image"
}
}`
}
func dockerDeletedEventJSON() string {
return `
{
"domain": "docker",
"event_type": "deleted",
"data": {
"image_name": "sample_arch",
"name": "sample.txt",
"path": "sample_dir/sample.txt",
"platforms": [
{
"architecture": "sample_os",
"os": "sample_tag"
}
],
"repo_key": "sample_repo",
"sha256": "sample_checksum",
"size": 0,
"tag": "sample_image"
}
}`
}
func dockerPromotedEventJSON() string {
return `
{
"domain": "docker",
"event_type": "promoted",
"data": {
"image_name": "sample_arch",
"name": "sample.txt",
"path": "sample_dir/sample.txt",
"platforms": [
{
"architecture": "sample_os",
"os": "sample_tag"
}
],
"repo_key": "sample_repo",
"sha256": "sample_checksum",
"size": 0,
"tag": "sample_image"
}
}`
}
func buildUploadedEventJSON() string {
return `
{
"domain": "build",
"event_type": "uploaded",
"data": {
"build_name": "sample_build_name",
"build_number": "1",
"build_started": "1970-01-01T00:00:00.000+0000"
}
}`
}
func buildDeletedEventJSON() string {
return `
{
"domain": "build",
"event_type": "deleted",
"data": {
"build_name": "sample_build_name",
"build_number": "1",
"build_started": "1970-01-01T00:00:00.000+0000"
}
}`
}
func buildPromotedEventJSON() string {
return `
{
"domain": "build",
"event_type": "promoted",
"data": {
"build_name": "sample_build_name",
"build_number": "1",
"build_started": "1970-01-01T00:00:00.000+0000"
}
}`
}
func releaseBundleCreatedEventJSON() string {
return `
{
"domain": "release_bundle",
"event_type": "created",
"destination": "release_bundle",
"data": {
"release_bundle_name": "sample_name",
"release_bundle_size": 9800,
"release_bundle_version": "1.0.0"
},
"jpd_origin": "https://dist-pipe2.jfrogdev.co/artifactory"
}`
}
func releaseBundleSignedEventJSON() string {
return `
{
"domain": "release_bundle",
"event_type": "signed",
"destination": "release_bundle",
"data": {
"release_bundle_name": "sample_name",
"release_bundle_size": 9800,
"release_bundle_version": "1.0.0"
},
"jpd_origin": "https://dist-pipe2.jfrogdev.co/artifactory"
}`
}
func releaseBundleDeletedEventJSON() string {
return `
{
"domain": "release_bundle",
"event_type": "signed",
"destination": "release_bundle",
"data": {
"release_bundle_name": "sample_name",
"release_bundle_size": 9800,
"release_bundle_version": "1.0.0"
},
"jpd_origin": "https://dist-pipe2.jfrogdev.co/artifactory"
}`
}
func distributionStartedEventJSON() string {
return `
{
"domain": "distribution",
"event_type": "distribute_started",
"destination": "distribution",
"data": {
"edge_node_info_list": [
{
"edge_node_address": "https://artifactory-edge2-dev.jfrogdev.co/artifactory",
"edge_node_name": "artifactory-edge2"
},
{
"edge_node_address": "https://artifactory-edge1-dev.jfrogdev.co/artifactory",
"edge_node_name": "artifactory-edge1"
}
],
"release_bundle_name": "test",
"release_bundle_size": 1037976,
"release_bundle_version": "1.0.0",
"status_message": "CREATED",
"transaction_id": 395969746957422600
},
"jpd_origin": "https://ga-dev.jfrogdev.co/artifactory"
}`
}
func distributionCompletedEventJSON() string {
return `
{
"domain": "distribution",
"event_type": "distribute_completed",
"destination": "distribution",
"data": {
"edge_node_info_list": [
{
"edge_node_address": "https://artifactory-edge2-dev.jfrogdev.co/artifactory",
"edge_node_name": "artifactory-edge2"
},
{
"edge_node_address": "https://artifactory-edge1-dev.jfrogdev.co/artifactory",
"edge_node_name": "artifactory-edge1"
}
],
"release_bundle_name": "test",
"release_bundle_size": 1037976,
"release_bundle_version": "1.0.0",
"status_message": "CREATED",
"transaction_id": 395969746957422600
},
"jpd_origin": "https://ga-dev.jfrogdev.co/artifactory"
}`
}
func distributionAbortedEventJSON() string {
return `
{
"domain": "distribution",
"event_type": "distribute_aborted",
"destination": "distribution",
"data": {
"edge_node_info_list": [
{
"edge_node_address": "https://artifactory-edge2-dev.jfrogdev.co/artifactory",
"edge_node_name": "artifactory-edge2"
},
{
"edge_node_address": "https://artifactory-edge1-dev.jfrogdev.co/artifactory",
"edge_node_name": "artifactory-edge1"
}
],
"release_bundle_name": "test",
"release_bundle_size": 1037976,
"release_bundle_version": "1.0.0",
"status_message": "CREATED",
"transaction_id": 395969746957422600
},
"jpd_origin": "https://ga-dev.jfrogdev.co/artifactory"
}`
}
func distributionFailedEventJSON() string {
return `
{
"domain": "distribution",
"event_type": "distribute_failed",
"destination": "distribution",
"data": {
"edge_node_info_list": [
{
"edge_node_address": "https://artifactory-edge2-dev.jfrogdev.co/artifactory",
"edge_node_name": "artifactory-edge2"
},
{
"edge_node_address": "https://artifactory-edge1-dev.jfrogdev.co/artifactory",
"edge_node_name": "artifactory-edge1"
}
],
"release_bundle_name": "test",
"release_bundle_size": 1037976,
"release_bundle_version": "1.0.0",
"status_message": "CREATED",
"transaction_id": 395969746957422600
},
"jpd_origin": "https://ga-dev.jfrogdev.co/artifactory"
}`
}
func destinationReceivedEventJSON() string {
return `
{
"domain": "destination",
"event_type": "received",
"destination": "artifactory_release_bundle",
"data": {
"release_bundle_name": "test",
"release_bundle_version": "1.0.0",
"status_message": "COMPLETED"
},
"jpd_origin": "https://dist-pipe2.jfrogdev.co/artifactory"
}`
}
func destinationDeleteStartedEventJSON() string {
return `
{
"domain": "destination",
"event_type": "delete_started",
"destination": "artifactory_release_bundle",
"data": {
"release_bundle_name": "test",
"release_bundle_version": "1.0.0",
"status_message": "COMPLETED"
},
"jpd_origin": "https://dist-pipe2.jfrogdev.co/artifactory"
}`
}
func destinationDeleteCompletedEventJSON() string {
return `
{
"domain": "destination",
"event_type": "delete_completed",
"destination": "artifactory_release_bundle",
"data": {
"release_bundle_name": "test",
"release_bundle_version": "1.0.0",
"status_message": "COMPLETED"
},
"jpd_origin": "https://dist-pipe2.jfrogdev.co/artifactory"
}`
}
func destinationDeleteFailedEventJSON() string {
return `
{
"domain": "destination",
"event_type": "delete_failed",
"destination": "artifactory_release_bundle",
"data": {
"release_bundle_name": "test",
"release_bundle_version": "1.0.0",
"status_message": "COMPLETED"
},
"jpd_origin": "https://dist-pipe2.jfrogdev.co/artifactory"
}`
}

View file

@ -0,0 +1,255 @@
package artifactory
import (
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
)
const meas = "artifactory_webhooks"
type event interface {
newMetric() telegraf.Metric
}
type artifactDeploymentOrDeletedEvent struct {
Domain string `json:"domain"`
Event string `json:"event_type"`
Data struct {
Repo string `json:"repo_key"`
Path string `json:"path"`
Name string `json:"name"`
Size int64 `json:"size"`
Sha string `json:"sha256"`
} `json:"data"`
}
func (e artifactDeploymentOrDeletedEvent) newMetric() telegraf.Metric {
t := map[string]string{
"domain": e.Domain,
"event_type": e.Event,
"repo": e.Data.Repo,
"path": e.Data.Path,
"name": e.Data.Name,
}
f := map[string]interface{}{
"size": e.Data.Size,
"sha256": e.Data.Sha,
}
return metric.New(meas, t, f, time.Now())
}
type artifactMovedOrCopiedEvent struct {
Domain string `json:"domain"`
Event string `json:"event_type"`
Data struct {
Repo string `json:"repo_key"`
Path string `json:"path"`
Name string `json:"name"`
Size int64 `json:"size"`
SourcePath string `json:"source_repo_path"`
TargetPath string `json:"target_repo_path"`
} `json:"data"`
}
func (e artifactMovedOrCopiedEvent) newMetric() telegraf.Metric {
t := map[string]string{
"domain": e.Domain,
"event_type": e.Event,
"repo": e.Data.Repo,
"path": e.Data.Path,
"name": e.Data.Name,
}
f := map[string]interface{}{
"size": e.Data.Size,
"source_path": e.Data.SourcePath,
"target_path": e.Data.TargetPath,
}
return metric.New(meas, t, f, time.Now())
}
type artifactPropertiesEvent struct {
Domain string `json:"domain"`
Event string `json:"event_type"`
Data struct {
Repo string `json:"repo_key"`
Path string `json:"path"`
Name string `json:"name"`
Size int64 `json:"size"`
PropertyKey string `json:"property_key"`
PropertyValues []string `json:"property_values"`
}
}
func (e artifactPropertiesEvent) newMetric() telegraf.Metric {
t := map[string]string{
"domain": e.Domain,
"event_type": e.Event,
"repo": e.Data.Repo,
"path": e.Data.Path,
"name": e.Data.Name,
}
f := map[string]interface{}{
"property_key": e.Data.PropertyKey,
"property_values": strings.Join(e.Data.PropertyValues, ","),
}
return metric.New(meas, t, f, time.Now())
}
type dockerEvent struct {
Domain string `json:"domain"`
Event string `json:"event_type"`
Data struct {
Repo string `json:"repo_key"`
Path string `json:"path"`
Name string `json:"name"`
Size int64 `json:"size"`
Sha string `json:"sha256"`
ImageName string `json:"image_name"`
Tag string `json:"tag"`
Platforms []struct {
Architecture string `json:"architecture"`
Os string `json:"os"`
} `json:"platforms"`
} `json:"data"`
}
func (e dockerEvent) newMetric() telegraf.Metric {
t := map[string]string{
"domain": e.Domain,
"event_type": e.Event,
"repo": e.Data.Repo,
"path": e.Data.Path,
"name": e.Data.Name,
"image_name": e.Data.ImageName,
}
f := map[string]interface{}{
"size": e.Data.Size,
"sha256": e.Data.Sha,
"tag": e.Data.Tag,
"platforms": e.Data.Platforms,
}
return metric.New(meas, t, f, time.Now())
}
type buildEvent struct {
Domain string `json:"domain"`
Event string `json:"event_type"`
Data struct {
BuildName string `json:"build_name"`
BuildNumber string `json:"build_number"`
BuildStarted string `json:"build_started"`
} `json:"data"`
}
func (e buildEvent) newMetric() telegraf.Metric {
t := map[string]string{
"domain": e.Domain,
"event_type": e.Event,
}
f := map[string]interface{}{
"build_name": e.Data.BuildName,
"build_number": e.Data.BuildNumber,
"build_started": e.Data.BuildStarted,
}
return metric.New(meas, t, f, time.Now())
}
type releaseBundleEvent struct {
Domain string `json:"domain"`
Event string `json:"event_type"`
Destination string `json:"destination"`
Data struct {
ReleaseBundleName string `json:"release_bundle_name"`
ReleaseBundleSize int64 `json:"release_bundle_size"`
ReleaseBundleVersion string `json:"release_bundle_version"`
} `json:"data"`
JpdOrigin string `json:"jpd_origin"`
}
func (e releaseBundleEvent) newMetric() telegraf.Metric {
t := map[string]string{
"domain": e.Domain,
"event_type": e.Event,
"destination": e.Destination,
"release_bundle_name": e.Data.ReleaseBundleName,
}
f := map[string]interface{}{
"release_bundle_size": e.Data.ReleaseBundleSize,
"release_bundle_version": e.Data.ReleaseBundleVersion,
"jpd_origin": e.JpdOrigin,
}
return metric.New(meas, t, f, time.Now())
}
type distributionEvent struct {
Domain string `json:"domain"`
Event string `json:"event_type"`
Destination string `json:"destination"`
Data struct {
EdgeNodeInfoList []struct {
EdgeNodeAddress string `json:"edge_node_address"`
EdgeNodeName string `json:"edge_node_name"`
} `json:"edge_node_info_list"`
Name string `json:"release_bundle_name"`
Size int64 `json:"release_bundle_size"`
Version string `json:"release_bundle_version"`
Message string `json:"status_message"`
TransactionID int64 `json:"transaction_id"`
} `json:"data"`
OriginURL string `json:"jpd_origin"`
}
func (e distributionEvent) newMetric() telegraf.Metric {
t := map[string]string{
"domain": e.Domain,
"event_type": e.Event,
"destination": e.Destination,
"release_bundle_name": e.Data.Name,
}
f := map[string]interface{}{
"release_bundle_size": e.Data.Size,
"release_bundle_version": e.Data.Version,
"status_message": e.Data.Message,
"transaction_id": e.Data.TransactionID,
"edge_node_info_list": e.Data.EdgeNodeInfoList,
"jpd_origin": e.OriginURL,
}
return metric.New(meas, t, f, time.Now())
}
type destinationEvent struct {
Domain string `json:"domain"`
Event string `json:"event_type"`
Destination string `json:"destination"`
Data struct {
Name string `json:"release_bundle_name"`
Version string `json:"release_bundle_version"`
Message string `json:"status_message"`
} `json:"data"`
OriginURL string `json:"jpd_origin"`
}
func (e destinationEvent) newMetric() telegraf.Metric {
t := map[string]string{
"domain": e.Domain,
"event_type": e.Event,
"destination": e.Destination,
"release_bundle_name": e.Data.Name,
}
f := map[string]interface{}{
"release_bundle_version": e.Data.Version,
"status_message": e.Data.Message,
"jpd_origin": e.OriginURL,
}
return metric.New(meas, t, f, time.Now())
}

View file

@ -0,0 +1,163 @@
package artifactory
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
func artifactoryWebhookRequest(t *testing.T, domain, event, jsonString string) {
var acc testutil.Accumulator
awh := &Webhook{Path: "/artifactory", acc: &acc, log: testutil.Logger{}}
req, err := http.NewRequest("POST", "/artifactory", strings.NewReader(jsonString))
require.NoError(t, err)
w := httptest.NewRecorder()
awh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST "+domain+":"+event+" returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
}
func artifactoryWebhookRequestWithSignature(t *testing.T, event, jsonString, signature string, expectedStatus int) {
var acc testutil.Accumulator
awh := &Webhook{Path: "/artifactory", acc: &acc, log: testutil.Logger{}}
req, err := http.NewRequest("POST", "/artifactory", strings.NewReader(jsonString))
require.NoError(t, err)
req.Header.Add("x-jfrog-event-auth", signature)
w := httptest.NewRecorder()
awh.eventHandler(w, req)
if w.Code != expectedStatus {
t.Errorf("POST "+event+" returned HTTP status code %v.\nExpected %v", w.Code, expectedStatus)
}
}
func TestUnsupportedEvent(t *testing.T) {
var acc testutil.Accumulator
awh := &Webhook{Path: "/artifactory", acc: &acc, log: testutil.Logger{}}
req, err := http.NewRequest("POST", "/artifactory", strings.NewReader(unsupportedEventJSON()))
require.NoError(t, err)
w := httptest.NewRecorder()
awh.eventHandler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("POST returned HTTP status code %v.\nExpected %v", w.Code, http.StatusBadRequest)
}
}
func TestArtifactDeployedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "artifact", "deployed", artifactDeployedEventJSON())
}
func TestArtifactDeleted(t *testing.T) {
artifactoryWebhookRequest(t, "artifact", "deleted", artifactDeletedEventJSON())
}
func TestArtifactMovedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "artifact", "moved", artifactMovedEventJSON())
}
func TestArtifactCopiedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "artifact", "copied", artifactCopiedEventJSON())
}
func TestArtifactPropertiesAddedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "artifact_property", "added", artifactPropertiesAddedEventJSON())
}
func TestArtifactPropertiesDeletedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "artifact_property", "deleted", artifactPropertiesDeletedEventJSON())
}
func TestDockerPushedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "docker", "pushed", dockerPushedEventJSON())
}
func TestDockerDeletedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "docker", "deleted", dockerDeletedEventJSON())
}
func TestDockerPromotedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "docker", "promoted", dockerPromotedEventJSON())
}
func TestBuildUploadedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "build", "uploaded", buildUploadedEventJSON())
}
func TestBuildDeletedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "build", "deleted", buildDeletedEventJSON())
}
func TestBuildPromotedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "build", "promoted", buildPromotedEventJSON())
}
func TestReleaseBundleCreatedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "release_bundle", "created", releaseBundleCreatedEventJSON())
}
func TestReleaseBundleSignedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "release_bundle", "signed", releaseBundleSignedEventJSON())
}
func TestReleaseBundleDeletedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "release_bundle", "deleted", releaseBundleDeletedEventJSON())
}
func TestDistributionStartedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "distribution", "distribute_started", distributionStartedEventJSON())
}
func TestDistributionCompletedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "distribution", "distribute_started", distributionCompletedEventJSON())
}
func TestDistributionAbortedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "distribution", "distribute_aborted", distributionAbortedEventJSON())
}
func TestDistributionFailedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "distribution", "distribute_failed", distributionFailedEventJSON())
}
func TestDestinationReceivedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "destination", "received", destinationReceivedEventJSON())
}
func TestDestinationDeletedStartedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "destination", "delete_started", destinationDeleteStartedEventJSON())
}
func TestDestinationDeletedCompletedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "destination", "delete_completed", destinationDeleteCompletedEventJSON())
}
func TestDestinationDeleteFailedEvent(t *testing.T) {
artifactoryWebhookRequest(t, "destination", "delete_failed", destinationDeleteFailedEventJSON())
}
func TestEventWithSignatureSuccess(t *testing.T) {
artifactoryWebhookRequestWithSignature(
t,
"watch",
artifactDeployedEventJSON(),
generateSignature("signature", []byte(artifactDeployedEventJSON())),
http.StatusOK,
)
}
func TestCheckSignatureSuccess(t *testing.T) {
if !checkSignature("my_little_secret", []byte("random-signature-body"), "sha1=3dca279e731c97c38e3019a075dee9ebbd0a99f0") {
t.Errorf("check signature failed")
}
}
func TestCheckSignatureFailed(t *testing.T) {
if checkSignature("m_little_secret", []byte("random-signature-body"), "sha1=3dca279e731c97c38e3019a075dee9ebbd0a99f0") {
t.Errorf("check signature failed")
}
}

View file

@ -0,0 +1,22 @@
# Filestack webhook
You should configure your Filestack's Webhooks to point at the `webhooks`
service. To do this go to [filestack.com](https://www.filestack.com/), select
your app and click `Credentials > Webhooks`. In the resulting page, set the
`URL` to `http://<my_ip>:1619/filestack`, and click on `Add`.
## Events
See the [webhook doc](https://www.filestack.com/docs/webhooks).
*Limitations*: It stores all events except video conversions events.
All events for logs the original timestamp, the action and the id.
**Tags:**
* 'action' = `event.action` string
**Fields:**
* 'id' = `event.id` string

View file

@ -0,0 +1,55 @@
package filestack
import (
"encoding/json"
"io"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/common/auth"
)
type Webhook struct {
Path string
acc telegraf.Accumulator
log telegraf.Logger
auth.BasicAuth
}
// Register registers the webhook with the provided router
func (fs *Webhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) {
router.HandleFunc(fs.Path, fs.eventHandler).Methods("POST")
fs.log = log
fs.log.Infof("Started the webhooks_filestack on %s", fs.Path)
fs.acc = acc
}
func (fs *Webhook) eventHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if !fs.Verify(r) {
w.WriteHeader(http.StatusUnauthorized)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
event := &filestackEvent{}
err = json.Unmarshal(body, event)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
fs.acc.AddFields("filestack_webhooks", event.fields(), event.tags(), time.Unix(event.TimeStamp, 0))
w.WriteHeader(http.StatusOK)
}

View file

@ -0,0 +1,21 @@
package filestack
import "strconv"
type filestackEvent struct {
Action string `json:"action"`
TimeStamp int64 `json:"timestamp"`
ID int `json:"id"`
}
func (fe *filestackEvent) tags() map[string]string {
return map[string]string{
"action": fe.Action,
}
}
func (fe *filestackEvent) fields() map[string]interface{} {
return map[string]interface{}{
"id": strconv.Itoa(fe.ID),
}
}

View file

@ -0,0 +1,86 @@
package filestack
import (
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
func postWebhooks(t *testing.T, md *Webhook, eventBodyFile io.Reader) *httptest.ResponseRecorder {
req, err := http.NewRequest("POST", "/filestack", eventBodyFile)
require.NoError(t, err)
w := httptest.NewRecorder()
md.eventHandler(w, req)
return w
}
func TestDialogEvent(t *testing.T) {
var acc testutil.Accumulator
fs := &Webhook{Path: "/filestack", acc: &acc}
resp := postWebhooks(t, fs, getFile(t, "testdata/dialog_open.json"))
if resp.Code != http.StatusOK {
t.Errorf("POST returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
fields := map[string]interface{}{
"id": "102",
}
tags := map[string]string{
"action": "fp.dialog",
}
acc.AssertContainsTaggedFields(t, "filestack_webhooks", fields, tags)
}
func TestParseError(t *testing.T) {
fs := &Webhook{Path: "/filestack"}
resp := postWebhooks(t, fs, strings.NewReader(""))
if resp.Code != http.StatusBadRequest {
t.Errorf("POST returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusBadRequest)
}
}
func TestUploadEvent(t *testing.T) {
var acc testutil.Accumulator
fs := &Webhook{Path: "/filestack", acc: &acc}
resp := postWebhooks(t, fs, getFile(t, "testdata/upload.json"))
if resp.Code != http.StatusOK {
t.Errorf("POST returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
fields := map[string]interface{}{
"id": "100946",
}
tags := map[string]string{
"action": "fp.upload",
}
acc.AssertContainsTaggedFields(t, "filestack_webhooks", fields, tags)
}
func TestVideoConversionEvent(t *testing.T) {
var acc testutil.Accumulator
fs := &Webhook{Path: "/filestack", acc: &acc}
resp := postWebhooks(t, fs, getFile(t, "testdata/video_conversion.json"))
if resp.Code != http.StatusBadRequest {
t.Errorf("POST returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusBadRequest)
}
}
func getFile(t *testing.T, filePath string) io.Reader {
file, err := os.Open(filePath)
require.NoErrorf(t, err, "could not read from file %s", filePath)
return file
}

View file

@ -0,0 +1,39 @@
{
"action": "fp.dialog",
"timestamp": 1435584646,
"id": 102,
"text": {
"mimetypes": [
"*/*"
],
"iframe": false,
"language": "en",
"id": "1435584650723",
"mobile": false,
"app": {
"upsell": "false",
"apikey": "YOUR_API_KEY",
"customization": {
"saveas_subheader": "Save it down to your local device or onto the Cloud",
"folder_subheader": "Choose a folder to share with this application",
"open_subheader": "Choose from the files on your local device or the ones you have online",
"folder_header": "Select a folder",
"help_text": "",
"saveas_header": "Save your file",
"open_header": "Upload a file"
}
},
"dialogType": "open",
"auth": false,
"welcome_header": "Upload a file",
"welcome_subheader": "Choose from the files on your local device or the ones you have online",
"help_text": "",
"recent_path": "/",
"extensions": null,
"maxSize": 0,
"signature": null,
"policy": null,
"custom_providers": "imgur,cloudapp",
"intra": false
}
}

View file

@ -0,0 +1,12 @@
{
"action": "fp.upload",
"timestamp": 1443444905,
"id": 100946,
"text": {
"url": "https://www.filestackapi.com/api/file/WAunDTTqQfCNWwUUyf6n",
"client": "Facebook",
"type": "image/jpeg",
"filename": "1579337399020824.jpg",
"size": 139154
}
}

View file

@ -0,0 +1,51 @@
{
"status":"completed",
"message":"Done",
"data":{
"thumb":"https://cdn.filestackcontent.com/f1e8V88QDuxzOvtOAq1W",
"thumb100x100":"https://process.filestackapi.com/AhTgLagciQByzXpFGRI0Az/resize=w:100,h:100,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/f1e8V88QDuxzOvtOAq1W",
"thumb200x200":"https://process.filestackapi.com/AhTgLagciQByzXpFGRI0Az/resize=w:200,h:200,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/f1e8V88QDuxzOvtOAq1W",
"thumb300x300":"https://process.filestackapi.com/AhTgLagciQByzXpFGRI0Az/resize=w:300,h:300,f:crop/output=f:jpg,q:66/https://cdn.filestackcontent.com/f1e8V88QDuxzOvtOAq1W",
"url":"https://cdn.filestackcontent.com/VgvFVdvvTkml0WXPIoGn"
},
"metadata":{
"result":{
"audio_channels":2,
"audio_codec":"vorbis",
"audio_sample_rate":44100,
"created_at":"2015/12/21 20:45:19 +0000",
"duration":10587,
"encoding_progress":100,
"encoding_time":8,
"extname":".webm",
"file_size":293459,
"fps":24,
"height":260,
"mime_type":"video/webm",
"started_encoding_at":"2015/12/21 20:45:22 +0000",
"updated_at":"2015/12/21 20:45:32 +0000",
"video_bitrate":221,
"video_codec":"vp8",
"width":300
},
"source":{
"audio_bitrate":125,
"audio_channels":2,
"audio_codec":"aac",
"audio_sample_rate":44100,
"created_at":"2015/12/21 20:45:19 +0000",
"duration":10564,
"extname":".mp4",
"file_size":875797,
"fps":24,
"height":360,
"mime_type":"video/mp4",
"updated_at":"2015/12/21 20:45:32 +0000",
"video_bitrate":196,
"video_codec":"h264",
"width":480
}
},
"timestamp":"1453850583",
"uuid":"638311d89d2bc849563a674a45809b7c"
}

View file

@ -0,0 +1,456 @@
# github webhooks
You should configure your Organization's Webhooks to point at the `webhooks`
service. To do this go to `github.com/{my_organization}` and click
`Settings > Webhooks > Add webhook`. In the resulting menu set `Payload URL` to
`http://<my_ip>:1619/github`, `Content type` to `application/json` and under
the section `Which events would you like to trigger this webhook?` select
'Send me **everything**'. By default all of the events will write to the
`github_webhooks` measurement, this is configurable by setting the
`measurement_name` in the config file.
You can also add a secret that will be used by telegraf to verify the
authenticity of the requests.
## Metrics
The titles of the following sections are links to the full payloads and details
for each event. The body contains what information from the event is persisted.
The format is as follows:
```toml
# TAGS
* 'tagKey' = `tagValue` type
# FIELDS
* 'fieldKey' = `fieldValue` type
```
The tag values and field values show the place on the incoming JSON object
where the data is sourced from.
### [`commit_comment` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#commit_comment)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'commit' = `event.comment.commit_id` string
* 'comment' = `event.comment.body` string
### [`create` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#create)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'ref' = `event.ref` string
* 'refType' = `event.ref_type` string
### [`delete` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#delete)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'ref' = `event.ref` string
* 'refType' = `event.ref_type` string
### [`deployment` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#deployment)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'commit' = `event.deployment.sha` string
* 'task' = `event.deployment.task` string
* 'environment' = `event.deployment.environment` string
* 'description' = `event.deployment.description` string
### [`deployment_status` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#deployment_status)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'commit' = `event.deployment.sha` string
* 'task' = `event.deployment.task` string
* 'environment' = `event.deployment.environment` string
* 'description' = `event.deployment.description` string
* 'depState' = `event.deployment_status.state` string
* 'depDescription' = `event.deployment_status.description` string
### [`fork` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#fork)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'forkee' = `event.forkee.repository` string
### [`gollum` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#gollum)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
### [`issue_comment` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#issue_comment)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
* 'issue' = `event.issue.number` int
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'title' = `event.issue.title` string
* 'comments' = `event.issue.comments` int
* 'body' = `event.comment.body` string
### [`issues` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#issues)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
* 'issue' = `event.issue.number` int
* 'action' = `event.action` string
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'title' = `event.issue.title` string
* 'comments' = `event.issue.comments` int
### [`member` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#member)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'newMember' = `event.sender.login` string
* 'newMemberStatus' = `event.sender.site_admin` bool
### [`membership` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#membership)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
* 'action' = `event.action` string
**Fields:**
* 'newMember' = `event.sender.login` string
* 'newMemberStatus' = `event.sender.site_admin` bool
### [`page_build` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#page_build)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
### [`public` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#public)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
### [`pull_request_review_comment` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request_review_comment)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'action' = `event.action` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
* 'prNumber' = `event.pull_request.number` int
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'state' = `event.pull_request.state` string
* 'title' = `event.pull_request.title` string
* 'comments' = `event.pull_request.comments` int
* 'commits' = `event.pull_request.commits` int
* 'additions' = `event.pull_request.additions` int
* 'deletions' = `event.pull_request.deletions` int
* 'changedFiles' = `event.pull_request.changed_files` int
* 'commentFile' = `event.comment.file` string
* 'comment' = `event.comment.body` string
### [`pull_request` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'action' = `event.action` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
* 'prNumber' = `event.pull_request.number` int
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'state' = `event.pull_request.state` string
* 'title' = `event.pull_request.title` string
* 'comments' = `event.pull_request.comments` int
* 'commits' = `event.pull_request.commits` int
* 'additions' = `event.pull_request.additions` int
* 'deletions' = `event.pull_request.deletions` int
* 'changedFiles' = `event.pull_request.changed_files` int
### [`push` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#push)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'ref' = `event.ref` string
* 'before' = `event.before` string
* 'after' = `event.after` string
### [`repository` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#repository)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
### [`release` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#release)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'tagName' = `event.release.tag_name` string
### [`status` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#status)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'commit' = `event.sha` string
* 'state' = `event.state` string
### [`team_add` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#team_add)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
* 'teamName' = `event.team.name` string
### [`watch` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#watch)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
**Fields:**
* 'stars' = `event.repository.stargazers_count` int
* 'forks' = `event.repository.forks_count` int
* 'issues' = `event.repository.open_issues_count` int
### [`workflow_job` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_job)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'action' = `event.action` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
* 'name' = `event.workflow_job.name` string
* 'conclusion' = `event.workflow_job.conclusion` string
**Fields:**
* 'run_attempt' = `event.workflow_job.run_attempt` int
* 'queue_time' = `event.workflow_job.started_at - event.workflow_job.created_at at event.action = in_progress in milliseconds` int
* 'run_time' = `event.workflow_job.completed_at - event.workflow_job.started_at at event.action = completed in milliseconds` int
* 'head_branch' = `event.workflow_job.head_branch` string
### [`workflow_run` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_run)
**Tags:**
* 'event' = `headers[X-Github-Event]` string
* 'action' = `event.action` string
* 'repository' = `event.repository.full_name` string
* 'private' = `event.repository.private` bool
* 'user' = `event.sender.login` string
* 'admin' = `event.sender.site_admin` bool
* 'name' = `event.workflow_run.name` string
* 'conclusion' = `event.workflow_run.conclusion` string
**Fields:**
* 'run_attempt' = `event.workflow_run.run_attempt` int
* 'run_time' = `event.workflow_run.completed_at - event.workflow_run.run_started_at at event.action = completed in milliseconds` int
* 'head_branch' = `event.workflow_run.head_branch` string

View file

@ -0,0 +1,150 @@
package github
import (
"crypto/hmac"
"crypto/sha1" //nolint:gosec // G505: Blocklisted import crypto/sha1: weak cryptographic primitive - sha1 hash is what is desired in this case
"encoding/hex"
"encoding/json"
"io"
"net/http"
"github.com/gorilla/mux"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/common/auth"
)
type Webhook struct {
Path string
secret string
acc telegraf.Accumulator
log telegraf.Logger
auth.BasicAuth
}
// Register registers the webhook with the provided router
func (gh *Webhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) {
router.HandleFunc(gh.Path, gh.eventHandler).Methods("POST")
gh.log = log
gh.log.Infof("Started the webhooks_github on %s", gh.Path)
gh.acc = acc
}
func (gh *Webhook) eventHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if !gh.Verify(r) {
w.WriteHeader(http.StatusUnauthorized)
return
}
eventType := r.Header.Get("X-Github-Event")
data, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if gh.secret != "" && !checkSignature(gh.secret, data, r.Header.Get("X-Hub-Signature")) {
gh.log.Error("Fail to check the github webhook signature")
w.WriteHeader(http.StatusBadRequest)
return
}
e, err := gh.newEvent(data, eventType)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if e != nil {
p := e.newMetric()
gh.acc.AddFields("github_webhooks", p.Fields(), p.Tags(), p.Time())
}
w.WriteHeader(http.StatusOK)
}
func generateEvent(data []byte, event event) (event, error) {
err := json.Unmarshal(data, event)
if err != nil {
return nil, err
}
return event, nil
}
type newEventError struct {
s string
}
func (e *newEventError) Error() string {
return e.s
}
func (gh *Webhook) newEvent(data []byte, name string) (event, error) {
gh.log.Debugf("New %v event received", name)
switch name {
case "commit_comment":
return generateEvent(data, &commitCommentEvent{})
case "create":
return generateEvent(data, &createEvent{})
case "delete":
return generateEvent(data, &deleteEvent{})
case "deployment":
return generateEvent(data, &deploymentEvent{})
case "deployment_status":
return generateEvent(data, &deploymentStatusEvent{})
case "fork":
return generateEvent(data, &forkEvent{})
case "gollum":
return generateEvent(data, &gollumEvent{})
case "issue_comment":
return generateEvent(data, &issueCommentEvent{})
case "issues":
return generateEvent(data, &issuesEvent{})
case "member":
return generateEvent(data, &memberEvent{})
case "membership":
return generateEvent(data, &membershipEvent{})
case "page_build":
return generateEvent(data, &pageBuildEvent{})
case "ping":
return nil, nil
case "public":
return generateEvent(data, &publicEvent{})
case "pull_request":
return generateEvent(data, &pullRequestEvent{})
case "pull_request_review_comment":
return generateEvent(data, &pullRequestReviewCommentEvent{})
case "push":
return generateEvent(data, &pushEvent{})
case "release":
return generateEvent(data, &releaseEvent{})
case "repository":
return generateEvent(data, &repositoryEvent{})
case "status":
return generateEvent(data, &statusEvent{})
case "team_add":
return generateEvent(data, &teamAddEvent{})
case "watch":
return generateEvent(data, &watchEvent{})
case "workflow_job":
return generateEvent(data, &workflowJobEvent{})
case "workflow_run":
return generateEvent(data, &workflowRunEvent{})
}
return nil, &newEventError{"Not a recognized event type"}
}
func checkSignature(secret string, data []byte, signature string) bool {
return hmac.Equal([]byte(signature), []byte(generateSignature(secret, data)))
}
func generateSignature(secret string, data []byte) string {
mac := hmac.New(sha1.New, []byte(secret))
if _, err := mac.Write(data); err != nil {
return err.Error()
}
result := mac.Sum(nil)
return "sha1=" + hex.EncodeToString(result)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,739 @@
package github
import (
"strconv"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
)
const meas = "github_webhooks"
type event interface {
newMetric() telegraf.Metric
}
type repository struct {
Repository string `json:"full_name"`
Private bool `json:"private"`
Stars int `json:"stargazers_count"`
Forks int `json:"forks_count"`
Issues int `json:"open_issues_count"`
}
type sender struct {
User string `json:"login"`
Admin bool `json:"site_admin"`
}
type commitComment struct {
Commit string `json:"commit_id"`
Body string `json:"body"`
}
type deployment struct {
Commit string `json:"sha"`
Task string `json:"task"`
Environment string `json:"environment"`
Description string `json:"description"`
}
type page struct {
Name string `json:"page_name"`
Title string `json:"title"`
Action string `json:"action"`
}
type issue struct {
Number int `json:"number"`
Title string `json:"title"`
Comments int `json:"comments"`
}
type issueComment struct {
Body string `json:"body"`
}
type team struct {
Name string `json:"name"`
}
type pullRequest struct {
Number int `json:"number"`
State string `json:"state"`
Title string `json:"title"`
Comments int `json:"comments"`
Commits int `json:"commits"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
ChangedFiles int `json:"changed_files"`
}
type pullRequestReviewComment struct {
File string `json:"path"`
Comment string `json:"body"`
}
type workflowJob struct {
RunAttempt int `json:"run_attempt"`
HeadBranch string `json:"head_branch"`
CreatedAt time.Time `json:"created_at"`
StartedAt time.Time `json:"started_at"`
CompletedAt time.Time `json:"completed_at"`
Name string `json:"name"`
Conclusion string `json:"conclusion"`
}
type workflowRun struct {
HeadBranch string `json:"head_branch"`
CreatedAt time.Time `json:"created_at"`
RunStartedAt time.Time `json:"run_started_at"`
UpdatedAt time.Time `json:"updated_at"`
RunAttempt int `json:"run_attempt"`
Name string `json:"name"`
Conclusion string `json:"conclusion"`
}
type release struct {
TagName string `json:"tag_name"`
}
type deploymentStatus struct {
State string `json:"state"`
Description string `json:"description"`
}
type commitCommentEvent struct {
Comment commitComment `json:"comment"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s commitCommentEvent) newMetric() telegraf.Metric {
event := "commit_comment"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"commit": s.Comment.Commit,
"comment": s.Comment.Body,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type createEvent struct {
Ref string `json:"ref"`
RefType string `json:"ref_type"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s createEvent) newMetric() telegraf.Metric {
event := "create"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"ref": s.Ref,
"refType": s.RefType,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type deleteEvent struct {
Ref string `json:"ref"`
RefType string `json:"ref_type"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s deleteEvent) newMetric() telegraf.Metric {
event := "delete"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"ref": s.Ref,
"refType": s.RefType,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type deploymentEvent struct {
Deployment deployment `json:"deployment"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s deploymentEvent) newMetric() telegraf.Metric {
event := "deployment"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"commit": s.Deployment.Commit,
"task": s.Deployment.Task,
"environment": s.Deployment.Environment,
"description": s.Deployment.Description,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type deploymentStatusEvent struct {
Deployment deployment `json:"deployment"`
DeploymentStatus deploymentStatus `json:"deployment_status"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s deploymentStatusEvent) newMetric() telegraf.Metric {
event := "delete"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"commit": s.Deployment.Commit,
"task": s.Deployment.Task,
"environment": s.Deployment.Environment,
"description": s.Deployment.Description,
"depState": s.DeploymentStatus.State,
"depDescription": s.DeploymentStatus.Description,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type forkEvent struct {
Forkee repository `json:"forkee"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s forkEvent) newMetric() telegraf.Metric {
event := "fork"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"fork": s.Forkee.Repository,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type gollumEvent struct {
Pages []page `json:"pages"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
// REVIEW: Going to be lazy and not deal with the pages.
func (s gollumEvent) newMetric() telegraf.Metric {
event := "gollum"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type issueCommentEvent struct {
Issue issue `json:"issue"`
Comment issueComment `json:"comment"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s issueCommentEvent) newMetric() telegraf.Metric {
event := "issue_comment"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
"issue": strconv.Itoa(s.Issue.Number),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"title": s.Issue.Title,
"comments": s.Issue.Comments,
"body": s.Comment.Body,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type issuesEvent struct {
Action string `json:"action"`
Issue issue `json:"issue"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s issuesEvent) newMetric() telegraf.Metric {
event := "issue"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
"issue": strconv.Itoa(s.Issue.Number),
"action": s.Action,
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"title": s.Issue.Title,
"comments": s.Issue.Comments,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type memberEvent struct {
Member sender `json:"member"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s memberEvent) newMetric() telegraf.Metric {
event := "member"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"newMember": s.Member.User,
"newMemberStatus": s.Member.Admin,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type membershipEvent struct {
Action string `json:"action"`
Member sender `json:"member"`
Sender sender `json:"sender"`
Team team `json:"team"`
}
func (s membershipEvent) newMetric() telegraf.Metric {
event := "membership"
t := map[string]string{
"event": event,
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
"action": s.Action,
}
f := map[string]interface{}{
"newMember": s.Member.User,
"newMemberStatus": s.Member.Admin,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type pageBuildEvent struct {
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s pageBuildEvent) newMetric() telegraf.Metric {
event := "page_build"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type publicEvent struct {
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s publicEvent) newMetric() telegraf.Metric {
event := "public"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type pullRequestEvent struct {
Action string `json:"action"`
PullRequest pullRequest `json:"pull_request"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s pullRequestEvent) newMetric() telegraf.Metric {
event := "pull_request"
t := map[string]string{
"event": event,
"action": s.Action,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
"prNumber": strconv.Itoa(s.PullRequest.Number),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"state": s.PullRequest.State,
"title": s.PullRequest.Title,
"comments": s.PullRequest.Comments,
"commits": s.PullRequest.Commits,
"additions": s.PullRequest.Additions,
"deletions": s.PullRequest.Deletions,
"changedFiles": s.PullRequest.ChangedFiles,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type pullRequestReviewCommentEvent struct {
Comment pullRequestReviewComment `json:"comment"`
PullRequest pullRequest `json:"pull_request"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s pullRequestReviewCommentEvent) newMetric() telegraf.Metric {
event := "pull_request_review_comment"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
"prNumber": strconv.Itoa(s.PullRequest.Number),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"state": s.PullRequest.State,
"title": s.PullRequest.Title,
"comments": s.PullRequest.Comments,
"commits": s.PullRequest.Commits,
"additions": s.PullRequest.Additions,
"deletions": s.PullRequest.Deletions,
"changedFiles": s.PullRequest.ChangedFiles,
"commentFile": s.Comment.File,
"comment": s.Comment.Comment,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type pushEvent struct {
Ref string `json:"ref"`
Before string `json:"before"`
After string `json:"after"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s pushEvent) newMetric() telegraf.Metric {
event := "push"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"ref": s.Ref,
"before": s.Before,
"after": s.After,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type releaseEvent struct {
Release release `json:"release"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s releaseEvent) newMetric() telegraf.Metric {
event := "release"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"tagName": s.Release.TagName,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type repositoryEvent struct {
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s repositoryEvent) newMetric() telegraf.Metric {
event := "repository"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type statusEvent struct {
Commit string `json:"sha"`
State string `json:"state"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s statusEvent) newMetric() telegraf.Metric {
event := "status"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"commit": s.Commit,
"state": s.State,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type teamAddEvent struct {
Team team `json:"team"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s teamAddEvent) newMetric() telegraf.Metric {
event := "team_add"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
"teamName": s.Team.Name,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type watchEvent struct {
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s watchEvent) newMetric() telegraf.Metric {
event := "delete"
t := map[string]string{
"event": event,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
}
f := map[string]interface{}{
"stars": s.Repository.Stars,
"forks": s.Repository.Forks,
"issues": s.Repository.Issues,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type workflowJobEvent struct {
Action string `json:"action"`
WorkflowJob workflowJob `json:"workflow_job"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s workflowJobEvent) newMetric() telegraf.Metric {
event := "workflow_job"
t := map[string]string{
"event": event,
"action": s.Action,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
"name": s.WorkflowJob.Name,
"conclusion": s.WorkflowJob.Conclusion,
}
var runTimeMs int64
var queueTimeMs int64
if s.Action == "in_progress" {
queueTimeMs = s.WorkflowJob.StartedAt.Sub(s.WorkflowJob.CreatedAt).Milliseconds()
}
if s.Action == "completed" {
runTimeMs = s.WorkflowJob.CompletedAt.Sub(s.WorkflowJob.StartedAt).Milliseconds()
}
f := map[string]interface{}{
"run_attempt": s.WorkflowJob.RunAttempt,
"queue_time": queueTimeMs,
"run_time": runTimeMs,
"head_branch": s.WorkflowJob.HeadBranch,
}
m := metric.New(meas, t, f, time.Now())
return m
}
type workflowRunEvent struct {
Action string `json:"action"`
WorkflowRun workflowRun `json:"workflow_run"`
Repository repository `json:"repository"`
Sender sender `json:"sender"`
}
func (s workflowRunEvent) newMetric() telegraf.Metric {
event := "workflow_run"
t := map[string]string{
"event": event,
"action": s.Action,
"repository": s.Repository.Repository,
"private": strconv.FormatBool(s.Repository.Private),
"user": s.Sender.User,
"admin": strconv.FormatBool(s.Sender.Admin),
"name": s.WorkflowRun.Name,
"conclusion": s.WorkflowRun.Conclusion,
}
var runTimeMs int64
if s.Action == "completed" {
runTimeMs = s.WorkflowRun.UpdatedAt.Sub(s.WorkflowRun.RunStartedAt).Milliseconds()
}
f := map[string]interface{}{
"run_attempt": s.WorkflowRun.RunAttempt,
"run_time": runTimeMs,
"head_branch": s.WorkflowRun.HeadBranch,
}
m := metric.New(meas, t, f, time.Now())
return m
}

View file

@ -0,0 +1,147 @@
package github
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
func githubWebhookRequest(t *testing.T, event, jsonString string) {
var acc testutil.Accumulator
gh := &Webhook{Path: "/github", acc: &acc, log: testutil.Logger{}}
req, err := http.NewRequest("POST", "/github", strings.NewReader(jsonString))
require.NoError(t, err)
req.Header.Add("X-Github-Event", event)
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("POST "+event+" returned HTTP status code %v.\nExpected %v", w.Code, http.StatusOK)
}
}
func githubWebhookRequestWithSignature(t *testing.T, event, jsonString, signature string, expectedStatus int) {
var acc testutil.Accumulator
gh := &Webhook{Path: "/github", secret: "signature", acc: &acc, log: testutil.Logger{}}
req, err := http.NewRequest("POST", "/github", strings.NewReader(jsonString))
require.NoError(t, err)
req.Header.Add("X-Github-Event", event)
req.Header.Add("X-Hub-Signature", signature)
w := httptest.NewRecorder()
gh.eventHandler(w, req)
if w.Code != expectedStatus {
t.Errorf("POST "+event+" returned HTTP status code %v.\nExpected %v", w.Code, expectedStatus)
}
}
func TestCommitCommentEvent(t *testing.T) {
githubWebhookRequest(t, "commit_comment", commitCommentEventJSON())
}
func TestPingEvent(t *testing.T) {
githubWebhookRequest(t, "ping", "")
}
func TestDeleteEvent(t *testing.T) {
githubWebhookRequest(t, "delete", deleteEventJSON())
}
func TestDeploymentEvent(t *testing.T) {
githubWebhookRequest(t, "deployment", deploymentEventJSON())
}
func TestDeploymentStatusEvent(t *testing.T) {
githubWebhookRequest(t, "deployment_status", deploymentStatusEventJSON())
}
func TestForkEvent(t *testing.T) {
githubWebhookRequest(t, "fork", forkEventJSON())
}
func TestGollumEvent(t *testing.T) {
githubWebhookRequest(t, "gollum", gollumEventJSON())
}
func TestIssueCommentEvent(t *testing.T) {
githubWebhookRequest(t, "issue_comment", issueCommentEventJSON())
}
func TestIssuesEvent(t *testing.T) {
githubWebhookRequest(t, "issues", issuesEventJSON())
}
func TestMemberEvent(t *testing.T) {
githubWebhookRequest(t, "member", memberEventJSON())
}
func TestMembershipEvent(t *testing.T) {
githubWebhookRequest(t, "membership", membershipEventJSON())
}
func TestPageBuildEvent(t *testing.T) {
githubWebhookRequest(t, "page_build", pageBuildEventJSON())
}
func TestPublicEvent(t *testing.T) {
githubWebhookRequest(t, "public", publicEventJSON())
}
func TestPullRequestReviewCommentEvent(t *testing.T) {
githubWebhookRequest(t, "pull_request_review_comment", pullRequestReviewCommentEventJSON())
}
func TestPushEvent(t *testing.T) {
githubWebhookRequest(t, "push", pushEventJSON())
}
func TestReleaseEvent(t *testing.T) {
githubWebhookRequest(t, "release", releaseEventJSON())
}
func TestRepositoryEvent(t *testing.T) {
githubWebhookRequest(t, "repository", repositoryEventJSON())
}
func TestStatusEvent(t *testing.T) {
githubWebhookRequest(t, "status", statusEventJSON())
}
func TestTeamAddEvent(t *testing.T) {
githubWebhookRequest(t, "team_add", teamAddEventJSON())
}
func TestWatchEvent(t *testing.T) {
githubWebhookRequest(t, "watch", watchEventJSON())
}
func TestEventWithSignatureFail(t *testing.T) {
githubWebhookRequestWithSignature(t, "watch", watchEventJSON(), "signature", http.StatusBadRequest)
}
func TestEventWithSignatureSuccess(t *testing.T) {
githubWebhookRequestWithSignature(t, "watch", watchEventJSON(), generateSignature("signature", []byte(watchEventJSON())), http.StatusOK)
}
func TestWorkflowJob(t *testing.T) {
githubWebhookRequest(t, "workflow_job", WorkflowJobJSON())
}
func TestWorkflowRun(t *testing.T) {
githubWebhookRequest(t, "workflow_run", WorkflowRunJSON())
}
func TestCheckSignatureSuccess(t *testing.T) {
if !checkSignature("my_little_secret", []byte("random-signature-body"), "sha1=3dca279e731c97c38e3019a075dee9ebbd0a99f0") {
t.Errorf("check signature failed")
}
}
func TestCheckSignatureFailed(t *testing.T) {
if checkSignature("m_little_secret", []byte("random-signature-body"), "sha1=3dca279e731c97c38e3019a075dee9ebbd0a99f0") {
t.Errorf("check signature failed")
}
}

View file

@ -0,0 +1,22 @@
# mandrill webhook
You should configure your Mandrill's Webhooks to point at the `webhooks`
service. To do this go to [mandrillapp.com](https://mandrillapp.com) and click
`Settings > Webhooks`. In the resulting page, click on `Add a Webhook`, select
all events, and set the `URL` to `http://<my_ip>:1619/mandrill`, and click on
`Create Webhook`.
## Events
See the [webhook doc](https://mandrill.zendesk.com/hc/en-us/articles/205583307-Message-Event-Webhook-format).
All events for logs the original timestamp, the event name and the unique
identifier of the message that generated the event.
**Tags:**
* 'event' = `event.event` string
**Fields:**
* 'id' = `event._id` string

View file

@ -0,0 +1,67 @@
package mandrill
import (
"encoding/json"
"io"
"net/http"
"net/url"
"time"
"github.com/gorilla/mux"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/common/auth"
)
type Webhook struct {
Path string
acc telegraf.Accumulator
log telegraf.Logger
auth.BasicAuth
}
// Register registers the webhook with the provided router
func (md *Webhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) {
router.HandleFunc(md.Path, returnOK).Methods("HEAD")
router.HandleFunc(md.Path, md.eventHandler).Methods("POST")
md.log = log
md.log.Infof("Started the webhooks_mandrill on %s", md.Path)
md.acc = acc
}
func returnOK(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}
func (md *Webhook) eventHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if !md.Verify(r) {
w.WriteHeader(http.StatusUnauthorized)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
data, err := url.ParseQuery(string(body))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
var events []mandrillEvent
err = json.Unmarshal([]byte(data.Get("mandrill_events")), &events)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
for _, event := range events {
md.acc.AddFields("mandrill_webhooks", event.fields(), event.tags(), time.Unix(event.TimeStamp, 0))
}
w.WriteHeader(http.StatusOK)
}

View file

@ -0,0 +1,19 @@
package mandrill
type mandrillEvent struct {
EventName string `json:"event"`
TimeStamp int64 `json:"ts"`
ID string `json:"_id"`
}
func (me *mandrillEvent) tags() map[string]string {
return map[string]string{
"event": me.EventName,
}
}
func (me *mandrillEvent) fields() map[string]interface{} {
return map[string]interface{}{
"id": me.ID,
}
}

View file

@ -0,0 +1,97 @@
package mandrill
import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
func postWebhooks(t *testing.T, md *Webhook, eventBody string) *httptest.ResponseRecorder {
body := url.Values{}
body.Set("mandrill_events", eventBody)
req, err := http.NewRequest("POST", "/mandrill", strings.NewReader(body.Encode()))
require.NoError(t, err)
w := httptest.NewRecorder()
md.eventHandler(w, req)
return w
}
func headRequest(t *testing.T) *httptest.ResponseRecorder {
req, err := http.NewRequest("HEAD", "/mandrill", strings.NewReader(""))
require.NoError(t, err)
w := httptest.NewRecorder()
returnOK(w, req)
return w
}
func TestHead(t *testing.T) {
resp := headRequest(t)
if resp.Code != http.StatusOK {
t.Errorf("HEAD returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
}
func TestSendEvent(t *testing.T) {
var acc testutil.Accumulator
md := &Webhook{Path: "/mandrill", acc: &acc}
resp := postWebhooks(t, md, "["+readFile(t, "testdata/send_event.json")+"]")
if resp.Code != http.StatusOK {
t.Errorf("POST send returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
fields := map[string]interface{}{
"id": "id1",
}
tags := map[string]string{
"event": "send",
}
acc.AssertContainsTaggedFields(t, "mandrill_webhooks", fields, tags)
}
func TestMultipleEvents(t *testing.T) {
var acc testutil.Accumulator
md := &Webhook{Path: "/mandrill", acc: &acc}
resp := postWebhooks(t, md, "["+readFile(t, "testdata/send_event.json")+","+readFile(t, "testdata/hard_bounce_event.json")+"]")
if resp.Code != http.StatusOK {
t.Errorf("POST send returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
fields := map[string]interface{}{
"id": "id1",
}
tags := map[string]string{
"event": "send",
}
acc.AssertContainsTaggedFields(t, "mandrill_webhooks", fields, tags)
fields = map[string]interface{}{
"id": "id2",
}
tags = map[string]string{
"event": "hard_bounce",
}
acc.AssertContainsTaggedFields(t, "mandrill_webhooks", fields, tags)
}
func readFile(t *testing.T, filePath string) string {
data, err := os.ReadFile(filePath)
require.NoErrorf(t, err, "could not read from file %s", filePath)
return string(data)
}

View file

@ -0,0 +1,23 @@
{
"event": "hard_bounce",
"msg": {
"ts": 1365109999,
"subject": "This an example webhook message",
"email": "example.webhook@mandrillapp.com",
"sender": "example.sender@mandrillapp.com",
"tags": [
"webhook-example"
],
"state": "bounced",
"metadata": {
"user_id": 111
},
"_id": "exampleaaaaaaaaaaaaaaaaaaaaaaaaa2",
"_version": "exampleaaaaaaaaaaaaaaa",
"bounce_description": "bad_mailbox",
"bgtools_code": 10,
"diag": "smtp;550 5.1.1 The email account that you tried to reach does not exist. Please try double-checking the recipient's email address for typos or unnecessary spaces."
},
"_id": "id2",
"ts": 1384954004
}

View file

@ -0,0 +1,26 @@
{
"event": "send",
"msg": {
"ts": 1365109999,
"subject": "This an example webhook message",
"email": "example.webhook@mandrillapp.com",
"sender": "example.sender@mandrillapp.com",
"tags": [
"webhook-example"
],
"opens": [
],
"clicks": [
],
"state": "sent",
"metadata": {
"user_id": 111
},
"_id": "exampleaaaaaaaaaaaaaaaaaaaaaaaaa",
"_version": "exampleaaaaaaaaaaaaaaa"
},
"_id": "id1",
"ts": 1384954004
}

View file

@ -0,0 +1,49 @@
# papertrail webhooks
Enables Telegraf to act as a [Papertrail Webhook](http://help.papertrailapp.com/kb/how-it-works/web-hooks/).
## Events
[Full documentation](http://help.papertrailapp.com/kb/how-it-works/web-hooks/#callback).
Events from Papertrail come in two forms:
* The [event-based callback](http://help.papertrailapp.com/kb/how-it-works/web-hooks/#callback):
* A point is created per event, with the timestamp as `received_at`
* Each point has a field counter (`count`), which is set to `1` (signifying
the event occurred)
* Each event "hostname" object is converted to a `host` tag
* The "saved_search" name in the payload is added as an `event` tag
* The "saved_search" id in the payload is added as a `search_id` field
* The papertrail url to view the event is built and added as a `url` field
* The rest of the event data is converted directly to fields on the point:
* `id`
* `source_ip`
* `source_name`
* `source_id`
* `program`
* `severity`
* `facility`
* `message`
When a callback is received, an event-based point will look similar to:
```shell
papertrail,host=myserver.example.com,event=saved_search_name count=1i,source_name="abc",program="CROND",severity="Info",source_id=2i,message="message body",source_ip="208.75.57.121",id=7711561783320576i,facility="Cron",url="https://papertrailapp.com/searches/42?centered_on_id=7711561783320576",search_id=42i 1453248892000000000
```
* The [count-based callback](http://help.papertrailapp.com/kb/how-it-works/web-hooks/#count-only-webhooks)
* A point is created per timeseries object per count, with the timestamp as
the "timeseries" key (the unix epoch of the event)
* Each point has a field counter (`count`), which is set to the value of each
"timeseries" object
* Each count "source_name" object is converted to a `host` tag
* The "saved_search" name in the payload is added as an `event` tag
When a callback is received, a count-based point will look similar to:
```shell
papertrail,host=myserver.example.com,event=saved_search_name count=3i 1453248892000000000
```

View file

@ -0,0 +1,207 @@
package papertrail
import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
const (
contentType = "application/x-www-form-urlencoded"
)
func post(t *testing.T, pt *Webhook, contentType, body string) *httptest.ResponseRecorder {
req, err := http.NewRequest("POST", "/", strings.NewReader(body))
require.NoError(t, err)
req.Header.Set("Content-Type", contentType)
w := httptest.NewRecorder()
pt.eventHandler(w, req)
return w
}
func TestWrongContentType(t *testing.T) {
var acc testutil.Accumulator
pt := &Webhook{Path: "/papertrail", acc: &acc}
form := url.Values{}
form.Set("payload", sampleEventPayload)
data := form.Encode()
resp := post(t, pt, "", data)
require.Equal(t, http.StatusUnsupportedMediaType, resp.Code)
}
func TestMissingPayload(t *testing.T) {
var acc testutil.Accumulator
pt := &Webhook{Path: "/papertrail", acc: &acc}
resp := post(t, pt, contentType, "")
require.Equal(t, http.StatusBadRequest, resp.Code)
}
func TestPayloadNotJSON(t *testing.T) {
var acc testutil.Accumulator
pt := &Webhook{Path: "/papertrail", acc: &acc}
resp := post(t, pt, contentType, "payload={asdf]")
require.Equal(t, http.StatusBadRequest, resp.Code)
}
func TestPayloadInvalidJSON(t *testing.T) {
var acc testutil.Accumulator
pt := &Webhook{Path: "/papertrail", acc: &acc}
resp := post(t, pt, contentType, `payload={"value": 42}`)
require.Equal(t, http.StatusBadRequest, resp.Code)
}
func TestEventPayload(t *testing.T) {
var acc testutil.Accumulator
pt := &Webhook{Path: "/papertrail", acc: &acc}
form := url.Values{}
form.Set("payload", sampleEventPayload)
resp := post(t, pt, contentType, form.Encode())
require.Equal(t, http.StatusOK, resp.Code)
fields1 := map[string]interface{}{
"count": uint64(1),
"id": int64(7711561783320576),
"source_ip": "208.75.57.121",
"source_name": "abc",
"source_id": int64(2),
"program": "CROND",
"severity": "Info",
"facility": "Cron",
"message": "message body",
"url": "https://papertrailapp.com/searches/42?centered_on_id=7711561783320576",
"search_id": int64(42),
}
fields2 := map[string]interface{}{
"count": uint64(1),
"id": int64(7711562567655424),
"source_ip": "208.75.57.120",
"source_name": "server1",
"source_id": int64(19),
"program": "CROND",
"severity": "Info",
"facility": "Cron",
"message": "A short event",
"url": "https://papertrailapp.com/searches/42?centered_on_id=7711562567655424",
"search_id": int64(42),
}
tags1 := map[string]string{
"event": "Important stuff",
"host": "abc",
}
tags2 := map[string]string{
"event": "Important stuff",
"host": "def",
}
acc.AssertContainsTaggedFields(t, "papertrail", fields1, tags1)
acc.AssertContainsTaggedFields(t, "papertrail", fields2, tags2)
}
func TestCountPayload(t *testing.T) {
var acc testutil.Accumulator
pt := &Webhook{Path: "/papertrail", acc: &acc}
form := url.Values{}
form.Set("payload", sampleCountPayload)
resp := post(t, pt, contentType, form.Encode())
require.Equal(t, http.StatusOK, resp.Code)
fields1 := map[string]interface{}{
"count": uint64(5),
}
fields2 := map[string]interface{}{
"count": uint64(3),
}
tags1 := map[string]string{
"event": "Important stuff",
"host": "arthur",
}
tags2 := map[string]string{
"event": "Important stuff",
"host": "ford",
}
acc.AssertContainsTaggedFields(t, "papertrail", fields1, tags1)
acc.AssertContainsTaggedFields(t, "papertrail", fields2, tags2)
}
const sampleEventPayload = `{
"events": [
{
"id": 7711561783320576,
"received_at": "2011-05-18T20:30:02-07:00",
"display_received_at": "May 18 20:30:02",
"source_ip": "208.75.57.121",
"source_name": "abc",
"source_id": 2,
"hostname": "abc",
"program": "CROND",
"severity": "Info",
"facility": "Cron",
"message": "message body"
},
{
"id": 7711562567655424,
"received_at": "2011-05-18T20:30:02-07:00",
"display_received_at": "May 18 20:30:02",
"source_ip": "208.75.57.120",
"source_name": "server1",
"source_id": 19,
"hostname": "def",
"program": "CROND",
"severity": "Info",
"facility": "Cron",
"message": "A short event"
}
],
"saved_search": {
"id": 42,
"name": "Important stuff",
"query": "cron OR server1",
"html_edit_url": "https://papertrailapp.com/searches/42/edit",
"html_search_url": "https://papertrailapp.com/searches/42"
},
"max_id": "7711582041804800",
"min_id": "7711561783320576"
}`
const sampleCountPayload = `{
"counts": [
{
"source_name": "arthur",
"source_id": 4,
"timeseries": {
"1453248895": 5
}
},
{
"source_name": "ford",
"source_id": 3,
"timeseries": {
"1453248927": 3
}
}
],
"saved_search": {
"id": 42,
"name": "Important stuff",
"query": "cron OR server1",
"html_edit_url": "https://papertrailapp.com/searches/42/edit",
"html_search_url": "https://papertrailapp.com/searches/42"
},
"max_id": "7711582041804800",
"min_id": "7711561783320576"
}`

View file

@ -0,0 +1,97 @@
package papertrail
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/common/auth"
)
type Webhook struct {
Path string
acc telegraf.Accumulator
log telegraf.Logger
auth.BasicAuth
}
// Register registers the webhook with the provided router
func (pt *Webhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) {
router.HandleFunc(pt.Path, pt.eventHandler).Methods("POST")
pt.log = log
pt.log.Infof("Started the papertrail_webhook on %s", pt.Path)
pt.acc = acc
}
func (pt *Webhook) eventHandler(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
return
}
if !pt.Verify(r) {
w.WriteHeader(http.StatusUnauthorized)
return
}
data := r.PostFormValue("payload")
if data == "" {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
var payload payload
err := json.Unmarshal([]byte(data), &payload)
if err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
if payload.Events != nil {
// Handle event-based payload
for _, e := range payload.Events {
// Warning: Duplicate event timestamps will overwrite each other
tags := map[string]string{
"host": e.Hostname,
"event": payload.SavedSearch.Name,
}
fields := map[string]interface{}{
"count": uint64(1),
"id": e.ID,
"source_ip": e.SourceIP,
"source_name": e.SourceName,
"source_id": int64(e.SourceID),
"program": e.Program,
"severity": e.Severity,
"facility": e.Facility,
"message": e.Message,
"url": fmt.Sprintf("%s?centered_on_id=%d", payload.SavedSearch.SearchURL, e.ID),
"search_id": payload.SavedSearch.ID,
}
pt.acc.AddFields("papertrail", fields, tags, e.ReceivedAt)
}
} else if payload.Counts != nil {
// Handle count-based payload
for _, c := range payload.Counts {
for ts, count := range *c.TimeSeries {
tags := map[string]string{
"host": c.SourceName,
"event": payload.SavedSearch.Name,
}
fields := map[string]interface{}{
"count": count,
}
pt.acc.AddFields("papertrail", fields, tags, time.Unix(ts, 0))
}
}
} else {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
}

View file

@ -0,0 +1,41 @@
package papertrail
import (
"time"
)
type event struct {
ID int64 `json:"id"`
ReceivedAt time.Time `json:"received_at"`
DisplayReceivedAt string `json:"display_received_at"`
SourceIP string `json:"source_ip"`
SourceName string `json:"source_name"`
SourceID int `json:"source_id"`
Hostname string `json:"hostname"`
Program string `json:"program"`
Severity string `json:"severity"`
Facility string `json:"facility"`
Message string `json:"message"`
}
type count struct {
SourceName string `json:"source_name"`
SourceID int64 `json:"source_id"`
TimeSeries *map[int64]uint64 `json:"timeseries"`
}
type savedSearch struct {
ID int64 `json:"id"`
Name string `json:"name"`
Query string `json:"query"`
EditURL string `json:"html_edit_url"`
SearchURL string `json:"html_search_url"`
}
type payload struct {
Events []*event `json:"events"`
Counts []*count `json:"counts"`
SavedSearch *savedSearch `json:"saved_search"`
MaxID string `json:"max_id"`
MinID string `json:"min_id"`
}

View file

@ -0,0 +1,45 @@
# particle webhooks
You should configure your Particle.io's Webhooks to point at the `webhooks`
service. To do this go to [https://console.particle.io][particle.io]
and click `Integrations > New Integration > Webhook`. In the resulting page set
`URL` to `http://<my_ip>:1619/particle`, and under `Advanced Settings` click
on `JSON` and add:
```json
{
"measurement": "your_measurement_name"
}
```
If required, enter your username and password, etc. and then click `Save`
[particle.io]: https://console.particle.io/
## Events
Your Particle device should publish an event that contains a JSON in the form
of:
```json
String data = String::format("{ \"tags\" : {
\"tag_name\": \"tag_value\",
\"other_tag\": \"other_value\"
},
\"values\": {
\"value_name\": %f,
\"other_value\": %f,
}
}", value_value, other_value
);
Particle.publish("event_name", data, PRIVATE);
```
Escaping the "" is required in the source file.
The number of tag values and field values is not restricted so you can send as
many values per webhook call as you'd like.
You will need to enable JSON messages in the Webhooks setup of Particle.io, and
make sure to check the "include default data" box as well.
See [webhook doc](https://docs.particle.io/reference/webhooks/)

View file

@ -0,0 +1,83 @@
package particle
import (
"encoding/json"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/common/auth"
)
type event struct {
Name string `json:"event"`
Data data `json:"data"`
TTL int `json:"ttl"`
PublishedAt string `json:"published_at"`
Measurement string `json:"measurement"`
}
type data struct {
Tags map[string]string `json:"tags"`
Fields map[string]interface{} `json:"values"`
}
func newEvent() *event {
return &event{
Data: data{
Tags: make(map[string]string),
Fields: make(map[string]interface{}),
},
}
}
func (e *event) time() (time.Time, error) {
return time.Parse("2006-01-02T15:04:05Z", e.PublishedAt)
}
type Webhook struct {
Path string
acc telegraf.Accumulator
log telegraf.Logger
auth.BasicAuth
}
// Register registers the webhook with the provided router
func (rb *Webhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) {
router.HandleFunc(rb.Path, rb.eventHandler).Methods("POST")
rb.log = log
rb.log.Infof("Started the webhooks_particle on %s", rb.Path)
rb.acc = acc
}
func (rb *Webhook) eventHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if !rb.Verify(r) {
w.WriteHeader(http.StatusUnauthorized)
return
}
e := newEvent()
if err := json.NewDecoder(r.Body).Decode(e); err != nil {
rb.acc.AddError(err)
w.WriteHeader(http.StatusBadRequest)
return
}
pTime, err := e.time()
if err != nil {
pTime = time.Now()
}
// Use 'measurement' event field as the measurement, or default to the event name.
measurementName := e.Measurement
if measurementName == "" {
measurementName = e.Name
}
rb.acc.AddFields(measurementName, e.Data.Fields, e.Data.Tags, pTime)
w.WriteHeader(http.StatusOK)
}

View file

@ -0,0 +1,144 @@
package particle
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
func postWebhooks(t *testing.T, rb *Webhook, eventBody string) *httptest.ResponseRecorder {
req, err := http.NewRequest("POST", "/", strings.NewReader(eventBody))
require.NoError(t, err)
w := httptest.NewRecorder()
w.Code = 500
rb.eventHandler(w, req)
return w
}
func TestNewItem(t *testing.T) {
t.Parallel()
var acc testutil.Accumulator
rb := &Webhook{Path: "/particle", acc: &acc}
resp := postWebhooks(t, rb, newItemJSON())
if resp.Code != http.StatusOK {
t.Errorf("POST new_item returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
fields := map[string]interface{}{
"temp_c": 26.680000,
"temp_f": 80.024001,
"infrared": 528.0,
"lux": 0.0,
"humidity": 44.937500,
"pressure": 998.998901,
"altitude": 119.331436,
"broadband": 1266.0,
}
tags := map[string]string{
"id": "230035001147343438323536",
"location": "TravelingWilbury",
}
acc.AssertContainsTaggedFields(t, "mydata", fields, tags)
}
func TestUnknowItem(t *testing.T) {
t.Parallel()
var acc testutil.Accumulator
rb := &Webhook{Path: "/particle", acc: &acc}
resp := postWebhooks(t, rb, unknownJSON())
if resp.Code != http.StatusOK {
t.Errorf("POST unknown returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
}
func TestDefaultMeasurementName(t *testing.T) {
t.Parallel()
var acc testutil.Accumulator
rb := &Webhook{Path: "/particle", acc: &acc}
resp := postWebhooks(t, rb, blankMeasurementJSON())
if resp.Code != http.StatusOK {
t.Errorf("POST new_item returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
fields := map[string]interface{}{
"temp_c": 26.680000,
}
tags := map[string]string{
"id": "230035001147343438323536",
}
acc.AssertContainsTaggedFields(t, "eventName", fields, tags)
}
func blankMeasurementJSON() string {
return `
{
"event": "eventName",
"data": {
"tags": {
"id": "230035001147343438323536"
},
"values": {
"temp_c": 26.680000
}
},
"ttl": 60,
"published_at": "2017-09-28T21:54:10.897Z",
"coreid": "123456789938323536",
"userid": "1234ee123ac8e5ec1231a123d",
"version": 10,
"public": false,
"productID": 1234,
"name": "sensor",
"measurement": ""
}`
}
func newItemJSON() string {
return `
{
"event": "temperature",
"data": {
"tags": {
"id": "230035001147343438323536",
"location": "TravelingWilbury"
},
"values": {
"temp_c": 26.680000,
"temp_f": 80.024001,
"humidity": 44.937500,
"pressure": 998.998901,
"altitude": 119.331436,
"broadband": 1266.0,
"infrared": 528.0,
"lux": 0.0
}
},
"ttl": 60,
"published_at": "2017-09-28T21:54:10.897Z",
"coreid": "123456789938323536",
"userid": "1234ee123ac8e5ec1231a123d",
"version": 10,
"public": false,
"productID": 1234,
"name": "sensor",
"measurement": "mydata"
}`
}
func unknownJSON() string {
return `
{
"event": "roger"
}`
}

View file

@ -0,0 +1,64 @@
# rollbar webhooks
You should configure your Rollbar's Webhooks to point at the `webhooks` service.
To do this go to [rollbar.com](https://rollbar.com/) and click
`Settings > Notifications > Webhook`. In the resulting page set `URL` to
`http://<my_ip>:1619/rollbar`, and click on `Enable Webhook Integration`.
## Events
The titles of the following sections are links to the full payloads and details
for each event. The body contains what information from the event is persisted.
The format is as follows:
```toml
# TAGS
* 'tagKey' = `tagValue` type
# FIELDS
* 'fieldKey' = `fieldValue` type
```
The tag values and field values show the place on the incoming JSON object where
the data is sourced from.
See [webhook doc](https://rollbar.com/docs/webhooks/)
### `new_item` event
**Tags:**
* 'event' = `event.event_name` string
* 'environment' = `event.data.item.environment` string
* 'project_id = `event.data.item.project_id` int
* 'language' = `event.data.item.last_occurrence.language` string
* 'level' = `event.data.item.last_occurrence.level` string
**Fields:**
* 'id' = `event.data.item.id` int
### `occurrence` event
**Tags:**
* 'event' = `event.event_name` string
* 'environment' = `event.data.item.environment` string
* 'project_id = `event.data.item.project_id` int
* 'language' = `event.data.occurrence.language` string
* 'level' = `event.data.occurrence.level` string
**Fields:**
* 'id' = `event.data.item.id` int
### `deploy` event
**Tags:**
* 'event' = `event.event_name` string
* 'environment' = `event.data.deploy.environment` string
* 'project_id = `event.data.deploy.project_id` int
**Fields:**
* 'id' = `event.data.item.id` int

View file

@ -0,0 +1,82 @@
package rollbar
import (
"encoding/json"
"errors"
"io"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/common/auth"
)
type Webhook struct {
Path string
acc telegraf.Accumulator
log telegraf.Logger
auth.BasicAuth
}
// Register registers the webhook with the provided router
func (rb *Webhook) Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger) {
router.HandleFunc(rb.Path, rb.eventHandler).Methods("POST")
rb.log = log
rb.log.Infof("Started the webhooks_rollbar on %s", rb.Path)
rb.acc = acc
}
func (rb *Webhook) eventHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if !rb.Verify(r) {
w.WriteHeader(http.StatusUnauthorized)
return
}
data, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
dummyEvent := &dummyEvent{}
err = json.Unmarshal(data, dummyEvent)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
event, err := newEvent(dummyEvent, data)
if err != nil {
w.WriteHeader(http.StatusOK)
return
}
rb.acc.AddFields("rollbar_webhooks", event.fields(), event.tags(), time.Now())
w.WriteHeader(http.StatusOK)
}
func generateEvent(event event, data []byte) (event, error) {
err := json.Unmarshal(data, event)
if err != nil {
return nil, err
}
return event, nil
}
func newEvent(dummyEvent *dummyEvent, data []byte) (event, error) {
switch dummyEvent.EventName {
case "new_item":
return generateEvent(&newItem{}, data)
case "occurrence":
return generateEvent(&occurrence{}, data)
case "deploy":
return generateEvent(&deploy{}, data)
default:
return nil, errors.New("Not implemented type: " + dummyEvent.EventName)
}
}

View file

@ -0,0 +1,115 @@
package rollbar
import "strconv"
type event interface {
tags() map[string]string
fields() map[string]interface{}
}
type dummyEvent struct {
EventName string `json:"event_name"`
}
type newItemDataItemLastOccurrence struct {
Language string `json:"language"`
Level string `json:"level"`
}
type newItemDataItem struct {
ID int `json:"id"`
Environment string `json:"environment"`
ProjectID int `json:"project_id"`
LastOccurrence newItemDataItemLastOccurrence `json:"last_occurrence"`
}
type newItemData struct {
Item newItemDataItem `json:"item"`
}
type newItem struct {
EventName string `json:"event_name"`
Data newItemData `json:"data"`
}
func (ni *newItem) tags() map[string]string {
return map[string]string{
"event": ni.EventName,
"environment": ni.Data.Item.Environment,
"project_id": strconv.Itoa(ni.Data.Item.ProjectID),
"language": ni.Data.Item.LastOccurrence.Language,
"level": ni.Data.Item.LastOccurrence.Level,
}
}
func (ni *newItem) fields() map[string]interface{} {
return map[string]interface{}{
"id": ni.Data.Item.ID,
}
}
type occurrenceDataOccurrence struct {
Language string `json:"language"`
Level string `json:"level"`
}
type occurrenceDataItem struct {
ID int `json:"id"`
Environment string `json:"environment"`
ProjectID int `json:"project_id"`
}
type occurrenceData struct {
Item occurrenceDataItem `json:"item"`
Occurrence occurrenceDataOccurrence `json:"occurrence"`
}
type occurrence struct {
EventName string `json:"event_name"`
Data occurrenceData `json:"data"`
}
func (o *occurrence) tags() map[string]string {
return map[string]string{
"event": o.EventName,
"environment": o.Data.Item.Environment,
"project_id": strconv.Itoa(o.Data.Item.ProjectID),
"language": o.Data.Occurrence.Language,
"level": o.Data.Occurrence.Level,
}
}
func (o *occurrence) fields() map[string]interface{} {
return map[string]interface{}{
"id": o.Data.Item.ID,
}
}
type deployDataDeploy struct {
ID int `json:"id"`
Environment string `json:"environment"`
ProjectID int `json:"project_id"`
}
type deployData struct {
Deploy deployDataDeploy `json:"deploy"`
}
type deploy struct {
EventName string `json:"event_name"`
Data deployData `json:"data"`
}
func (ni *deploy) tags() map[string]string {
return map[string]string{
"event": ni.EventName,
"environment": ni.Data.Deploy.Environment,
"project_id": strconv.Itoa(ni.Data.Deploy.ProjectID),
}
}
func (ni *deploy) fields() map[string]interface{} {
return map[string]interface{}{
"id": ni.Data.Deploy.ID,
}
}

View file

@ -0,0 +1,160 @@
package rollbar
func newItemJSON() string {
return `
{
"event_name": "new_item",
"data": {
"item": {
"public_item_id": null,
"integrations_data": {},
"last_activated_timestamp": 1382655421,
"unique_occurrences": null,
"id": 272716944,
"environment": "production",
"title": "testing aobg98wrwe",
"last_occurrence_id": 481761639,
"last_occurrence_timestamp": 1382655421,
"platform": 0,
"first_occurrence_timestamp": 1382655421,
"project_id": 90,
"resolved_in_version": null,
"status": 1,
"hash": "c595b2ae0af9b397bb6bdafd57104ac4d5f6b382",
"last_occurrence": {
"body": {
"message": {
"body": "testing aobg98wrwe"
}
},
"uuid": "d2036647-e0b7-4cad-bc98-934831b9b6d1",
"language": "python",
"level": "error",
"timestamp": 1382655421,
"server": {
"host": "dev",
"argv": [
""
]
},
"environment": "production",
"framework": "unknown",
"notifier": {
"version": "0.5.12",
"name": "pyrollbar"
},
"metadata": {
"access_token": "",
"debug": {
"routes": {
"start_time": 1382212080401,
"counters": {
"post_item": 3274122
}
}
},
"customer_timestamp": 1382655421,
"api_server_hostname": "web6"
}
},
"framework": 0,
"total_occurrences": 1,
"level": 40,
"counter": 4,
"first_occurrence_id": 481761639,
"activating_occurrence_id": 481761639
}
}
}`
}
func occurrenceJSON() string {
return `
{
"event_name": "occurrence",
"data": {
"item": {
"public_item_id": null,
"integrations_data": {},
"level_lock": 0,
"last_activated_timestamp": 1471624512,
"assigned_user_id": null,
"hash": "188fc37fa6e641a4d4a3d0198938a1937d31ddbe",
"id": 402860571,
"environment": "production",
"title": "Exception: test exception",
"last_occurrence_id": 16298872829,
"last_occurrence_timestamp": 1472226345,
"platform": 0,
"first_occurrence_timestamp": 1471624512,
"project_id": 78234,
"resolved_in_version": null,
"status": 1,
"unique_occurrences": null,
"title_lock": 0,
"framework": 6,
"total_occurrences": 8,
"level": 40,
"counter": 2,
"last_modified_by": 8247,
"first_occurrence_id": 16103102935,
"activating_occurrence_id": 16103102935
},
"occurrence": {
"body": {
"trace": {
"frames": [{"method": "<main>", "lineno": 27, "filename": "/Users/rebeccastandig/Desktop/Dev/php-rollbar-app/index.php"}], "exception": {
"message": "test 2",
"class": "Exception"}
}
},
"uuid": "84d4eccd-b24d-47ae-a42b-1a2f9a82fb82",
"language": "php",
"level": "error",
"timestamp": 1472226345,
"php_context": "cli",
"environment": "production",
"framework": "php",
"person": null,
"server": {
"host": "Rebeccas-MacBook-Pro.local",
"argv": ["index.php"]
},
"notifier": {
"version": "0.18.2",
"name": "rollbar-php"
},
"metadata": {
"customer_timestamp": 1472226359
}
}
}
}`
}
func deployJSON() string {
return `
{
"event_name": "deploy",
"data": {
"deploy": {
"comment": "deploying webs",
"user_id": 1,
"finish_time": 1382656039,
"start_time": 1382656038,
"id": 187585,
"environment": "production",
"project_id": 90,
"local_username": "brian",
"revision": "e4b9b7db860b2e5ac799f8c06b9498b71ab270bb"
}
}
}`
}
func unknownJSON() string {
return `
{
"event_name": "roger"
}`
}

View file

@ -0,0 +1,98 @@
package rollbar
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
func postWebhooks(t *testing.T, rb *Webhook, eventBody string) *httptest.ResponseRecorder {
req, err := http.NewRequest("POST", "/", strings.NewReader(eventBody))
require.NoError(t, err)
w := httptest.NewRecorder()
w.Code = 500
rb.eventHandler(w, req)
return w
}
func TestNewItem(t *testing.T) {
var acc testutil.Accumulator
rb := &Webhook{Path: "/rollbar", acc: &acc}
resp := postWebhooks(t, rb, newItemJSON())
if resp.Code != http.StatusOK {
t.Errorf("POST new_item returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
fields := map[string]interface{}{
"id": 272716944,
}
tags := map[string]string{
"event": "new_item",
"environment": "production",
"project_id": "90",
"language": "python",
"level": "error",
}
acc.AssertContainsTaggedFields(t, "rollbar_webhooks", fields, tags)
}
func TestOccurrence(t *testing.T) {
var acc testutil.Accumulator
rb := &Webhook{Path: "/rollbar", acc: &acc}
resp := postWebhooks(t, rb, occurrenceJSON())
if resp.Code != http.StatusOK {
t.Errorf("POST occurrence returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
fields := map[string]interface{}{
"id": 402860571,
}
tags := map[string]string{
"event": "occurrence",
"environment": "production",
"project_id": "78234",
"language": "php",
"level": "error",
}
acc.AssertContainsTaggedFields(t, "rollbar_webhooks", fields, tags)
}
func TestDeploy(t *testing.T) {
var acc testutil.Accumulator
rb := &Webhook{Path: "/rollbar", acc: &acc}
resp := postWebhooks(t, rb, deployJSON())
if resp.Code != http.StatusOK {
t.Errorf("POST deploy returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
fields := map[string]interface{}{
"id": 187585,
}
tags := map[string]string{
"event": "deploy",
"environment": "production",
"project_id": "90",
}
acc.AssertContainsTaggedFields(t, "rollbar_webhooks", fields, tags)
}
func TestUnknowItem(t *testing.T) {
rb := &Webhook{Path: "/rollbar"}
resp := postWebhooks(t, rb, unknownJSON())
if resp.Code != http.StatusOK {
t.Errorf("POST unknow returned HTTP status code %v.\nExpected %v", resp.Code, http.StatusOK)
}
}

View file

@ -0,0 +1,55 @@
# A Webhooks Event collector
[[inputs.webhooks]]
## Address and port to host Webhook listener on
service_address = ":1619"
## Maximum duration before timing out read of the request
# read_timeout = "10s"
## Maximum duration before timing out write of the response
# write_timeout = "10s"
[inputs.webhooks.filestack]
path = "/filestack"
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.github]
path = "/github"
# secret = ""
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.mandrill]
path = "/mandrill"
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.rollbar]
path = "/rollbar"
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.papertrail]
path = "/papertrail"
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.particle]
path = "/particle"
## HTTP basic auth
#username = ""
#password = ""
[inputs.webhooks.artifactory]
path = "/artifactory"

View file

@ -0,0 +1,136 @@
//go:generate ../../../tools/readme_config_includer/generator
package webhooks
import (
_ "embed"
"fmt"
"net"
"net/http"
"reflect"
"time"
"github.com/gorilla/mux"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/inputs"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/artifactory"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/filestack"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/github"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/mandrill"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/papertrail"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/particle"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/rollbar"
)
//go:embed sample.conf
var sampleConfig string
const (
defaultReadTimeout = 10 * time.Second
defaultWriteTimeout = 10 * time.Second
)
type Webhooks struct {
ServiceAddress string `toml:"service_address"`
ReadTimeout config.Duration `toml:"read_timeout"`
WriteTimeout config.Duration `toml:"write_timeout"`
Artifactory *artifactory.Webhook `toml:"artifactory"`
Filestack *filestack.Webhook `toml:"filestack"`
Github *github.Webhook `toml:"github"`
Mandrill *mandrill.Webhook `toml:"mandrill"`
Papertrail *papertrail.Webhook `toml:"papertrail"`
Particle *particle.Webhook `toml:"particle"`
Rollbar *rollbar.Webhook `toml:"rollbar"`
Log telegraf.Logger `toml:"-"`
srv *http.Server
}
// Webhook is an interface that all webhooks must implement
type Webhook interface {
// Register registers the webhook with the provided router
Register(router *mux.Router, acc telegraf.Accumulator, log telegraf.Logger)
}
func (*Webhooks) SampleConfig() string {
return sampleConfig
}
func (wb *Webhooks) Start(acc telegraf.Accumulator) error {
if wb.ReadTimeout < config.Duration(time.Second) {
wb.ReadTimeout = config.Duration(defaultReadTimeout)
}
if wb.WriteTimeout < config.Duration(time.Second) {
wb.WriteTimeout = config.Duration(defaultWriteTimeout)
}
r := mux.NewRouter()
for _, webhook := range wb.availableWebhooks() {
webhook.Register(r, acc, wb.Log)
}
wb.srv = &http.Server{
Handler: r,
ReadTimeout: time.Duration(wb.ReadTimeout),
WriteTimeout: time.Duration(wb.WriteTimeout),
}
ln, err := net.Listen("tcp", wb.ServiceAddress)
if err != nil {
return fmt.Errorf("error starting server: %w", err)
}
go func() {
if err := wb.srv.Serve(ln); err != nil {
if err != http.ErrServerClosed {
acc.AddError(fmt.Errorf("error listening: %w", err))
}
}
}()
wb.Log.Infof("Started the webhooks service on %s", wb.ServiceAddress)
return nil
}
func (*Webhooks) Gather(telegraf.Accumulator) error {
return nil
}
func (wb *Webhooks) Stop() {
wb.srv.Close()
wb.Log.Infof("Stopping the Webhooks service")
}
// availableWebhooks Looks for fields which implement Webhook interface
func (wb *Webhooks) availableWebhooks() []Webhook {
webhooks := make([]Webhook, 0)
s := reflect.ValueOf(wb).Elem()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
if !f.CanInterface() {
continue
}
if wbPlugin, ok := f.Interface().(Webhook); ok {
if !reflect.ValueOf(wbPlugin).IsNil() {
webhooks = append(webhooks, wbPlugin)
}
}
}
return webhooks
}
func newWebhooks() *Webhooks {
return &Webhooks{}
}
func init() {
inputs.Add("webhooks", func() telegraf.Input { return newWebhooks() })
}

View file

@ -0,0 +1,64 @@
package webhooks
import (
"reflect"
"testing"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/artifactory"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/filestack"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/github"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/mandrill"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/papertrail"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/particle"
"github.com/influxdata/telegraf/plugins/inputs/webhooks/rollbar"
)
func TestAvailableWebhooks(t *testing.T) {
wb := newWebhooks()
expected := make([]Webhook, 0)
if !reflect.DeepEqual(wb.availableWebhooks(), expected) {
t.Errorf("expected to %v.\nGot %v", expected, wb.availableWebhooks())
}
wb.Artifactory = &artifactory.Webhook{Path: "/artifactory"}
expected = append(expected, wb.Artifactory)
if !reflect.DeepEqual(wb.availableWebhooks(), expected) {
t.Errorf("expected to be %v.\nGot %v", expected, wb.availableWebhooks())
}
wb.Filestack = &filestack.Webhook{Path: "/filestack"}
expected = append(expected, wb.Filestack)
if !reflect.DeepEqual(wb.availableWebhooks(), expected) {
t.Errorf("expected to be %v.\nGot %v", expected, wb.availableWebhooks())
}
wb.Github = &github.Webhook{Path: "/github"}
expected = append(expected, wb.Github)
if !reflect.DeepEqual(wb.availableWebhooks(), expected) {
t.Errorf("expected to be %v.\nGot %v", expected, wb.availableWebhooks())
}
wb.Mandrill = &mandrill.Webhook{Path: "/mandrill"}
expected = append(expected, wb.Mandrill)
if !reflect.DeepEqual(wb.availableWebhooks(), expected) {
t.Errorf("expected to be %v.\nGot %v", expected, wb.availableWebhooks())
}
wb.Papertrail = &papertrail.Webhook{Path: "/papertrail"}
expected = append(expected, wb.Papertrail)
if !reflect.DeepEqual(wb.availableWebhooks(), expected) {
t.Errorf("expected to be %v.\nGot %v", expected, wb.availableWebhooks())
}
wb.Particle = &particle.Webhook{Path: "/particle"}
expected = append(expected, wb.Particle)
if !reflect.DeepEqual(wb.availableWebhooks(), expected) {
t.Errorf("expected to be %v.\nGot %v", expected, wb.availableWebhooks())
}
wb.Rollbar = &rollbar.Webhook{Path: "/rollbar"}
expected = append(expected, wb.Rollbar)
if !reflect.DeepEqual(wb.availableWebhooks(), expected) {
t.Errorf("expected to be %v.\nGot %v", expected, wb.availableWebhooks())
}
}