diff --git a/plugins/inputs/webhooks/README.md b/plugins/inputs/webhooks/README.md index 4ecba616af30a..16105c93eb979 100644 --- a/plugins/inputs/webhooks/README.md +++ b/plugins/inputs/webhooks/README.md @@ -63,6 +63,9 @@ sudo service telegraf start ## HTTP basic auth #username = "" #password = "" + + [inputs.webhooks.artifactory] + path = "/artifactory" ``` ## Available webhooks @@ -73,6 +76,7 @@ sudo service telegraf start - [Rollbar](rollbar/) - [Papertrail](papertrail/) - [Particle](particle/) +- [Artifactory](artifactory/) ## Adding new webhooks plugin diff --git a/plugins/inputs/webhooks/artifactory/README.md b/plugins/inputs/webhooks/artifactory/README.md new file mode 100644 index 0000000000000..4dabfacacdabf --- /dev/null +++ b/plugins/inputs/webhooks/artifactory/README.md @@ -0,0 +1,511 @@ +# artifactory webhook + +You need to configure to orginizations artifactory instance/s as detailed via the artifactory webhook documentation: . 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. + +## Events + +The different events type can be found found in the webhook documentation: . 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 seperated 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 seperated 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 + * 'achitecture' 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 + * 'achitecture' 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 + * 'achitecture' 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 diff --git a/plugins/inputs/webhooks/artifactory/artifactory_webhook.go b/plugins/inputs/webhooks/artifactory/artifactory_webhook.go new file mode 100644 index 0000000000000..95442f93e18d8 --- /dev/null +++ b/plugins/inputs/webhooks/artifactory/artifactory_webhook.go @@ -0,0 +1,120 @@ +package artifactory + +import ( + "crypto/hmac" + "crypto/sha1" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/gorilla/mux" + "github.com/influxdata/telegraf" +) + +type ArtifactoryWebhook struct { + Path string + Secret string + acc telegraf.Accumulator + log telegraf.Logger +} + +func (awh *ArtifactoryWebhook) 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 *ArtifactoryWebhook) 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 *ArtifactoryWebhook) NewEvent(data []byte, et string, 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{}) + } else if et == "moved" || et == "copied" { + return generateEvent(data, &ArtifactMovedOrCopiedEvent{}) + } else { + 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) +} diff --git a/plugins/inputs/webhooks/artifactory/artifactory_webhook_mock_json.go b/plugins/inputs/webhooks/artifactory/artifactory_webhook_mock_json.go new file mode 100644 index 0000000000000..0670132303947 --- /dev/null +++ b/plugins/inputs/webhooks/artifactory/artifactory_webhook_mock_json.go @@ -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" + }` +} diff --git a/plugins/inputs/webhooks/artifactory/artifactory_webhook_models.go b/plugins/inputs/webhooks/artifactory/artifactory_webhook_models.go new file mode 100644 index 0000000000000..7186794f5b0a4 --- /dev/null +++ b/plugins/inputs/webhooks/artifactory/artifactory_webhook_models.go @@ -0,0 +1,260 @@ +package artifactory + +import ( + "strings" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/metric" +) + +const meas = "artifactory_webhooks" + +type CommonFields struct { + Domain string `json:"domain"` + Event string `json:"event_type"` +} + +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:"achitecture"` + 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()) +} diff --git a/plugins/inputs/webhooks/artifactory/artifactory_webhook_test.go b/plugins/inputs/webhooks/artifactory/artifactory_webhook_test.go new file mode 100644 index 0000000000000..7c731487e236b --- /dev/null +++ b/plugins/inputs/webhooks/artifactory/artifactory_webhook_test.go @@ -0,0 +1,152 @@ +package artifactory + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/influxdata/telegraf/testutil" +) + +func ArtifactoryWebhookRequest(domain string, event string, jsonString string, t *testing.T) { + var acc testutil.Accumulator + awh := &ArtifactoryWebhook{Path: "/artifactory", acc: &acc, log: testutil.Logger{}} + req, _ := http.NewRequest("POST", "/artifactory", strings.NewReader(jsonString)) + 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(event string, jsonString string, t *testing.T, signature string, expectedStatus int) { + var acc testutil.Accumulator + awh := &ArtifactoryWebhook{Path: "/artifactory", acc: &acc, log: testutil.Logger{}} + req, _ := http.NewRequest("POST", "/artifactory", strings.NewReader(jsonString)) + 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 := &ArtifactoryWebhook{Path: "/artifactory", acc: &acc, log: testutil.Logger{}} + req, _ := http.NewRequest("POST", "/artifactory", strings.NewReader(UnsupportedEventJSON())) + 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("artifact", "deployed", ArtifactDeployedEventJSON(), t) +} + +func TestArtifactDeleted(t *testing.T) { + ArtifactoryWebhookRequest("artifact", "deleted", ArtifactDeletedEventJSON(), t) +} + +func TestArtifactMovedEvent(t *testing.T) { + ArtifactoryWebhookRequest("artifact", "moved", ArtifactMovedEventJSON(), t) +} + +func TestArtifactCopiedEvent(t *testing.T) { + ArtifactoryWebhookRequest("artifact", "copied", ArtifactCopiedEventJSON(), t) +} + +func TestArtifactPropertiesAddedEvent(t *testing.T) { + ArtifactoryWebhookRequest("artifact_property", "added", ArtifactPropertiesAddedEventJSON(), t) +} + +func TestArtifactPropertiesDeletedEvent(t *testing.T) { + ArtifactoryWebhookRequest("artifact_property", "deleted", ArtifactPropertiesDeletedEventJSON(), t) +} + +func TestDockerPushedEvent(t *testing.T) { + ArtifactoryWebhookRequest("docker", "pushed", DockerPushedEventJSON(), t) +} + +func TestDockerDeletedEvent(t *testing.T) { + ArtifactoryWebhookRequest("docker", "deleted", DockerDeletedEventJSON(), t) +} + +func TestDockerPromotedEvent(t *testing.T) { + ArtifactoryWebhookRequest("docker", "promoted", DockerPromotedEventJSON(), t) +} + +func TestBuildUploadedEvent(t *testing.T) { + ArtifactoryWebhookRequest("build", "uploaded", BuildUploadedEventJSON(), t) +} + +func TestBuildDeletedEvent(t *testing.T) { + ArtifactoryWebhookRequest("build", "deleted", BuildDeletedEventJSON(), t) +} + +func TestBuildPromotedEvent(t *testing.T) { + ArtifactoryWebhookRequest("build", "promoted", BuildPromotedEventJSON(), t) +} + +func TestReleaseBundleCreatedEvent(t *testing.T) { + ArtifactoryWebhookRequest("release_bundle", "created", ReleaseBundleCreatedEventJSON(), t) +} + +func TestReleaseBundleSignedEvent(t *testing.T) { + ArtifactoryWebhookRequest("release_bundle", "signed", ReleaseBundleSignedEventJSON(), t) +} + +func TestReleaseBundleDeletedEvent(t *testing.T) { + ArtifactoryWebhookRequest("release_bundle", "deleted", ReleaseBundleDeletedEventJSON(), t) +} + +func TestDistributionStartedEvent(t *testing.T) { + ArtifactoryWebhookRequest("distribution", "distribute_started", DistributionStartedEventJSON(), t) +} + +func TestDistributionCompletedEvent(t *testing.T) { + ArtifactoryWebhookRequest("distribution", "distribute_started", DistributionCompletedEventJSON(), t) +} + +func TestDistributionAbortedEvent(t *testing.T) { + ArtifactoryWebhookRequest("distribution", "distribute_aborted", DistributionAbortedEventJSON(), t) +} + +func TestDistributionFailedEvent(t *testing.T) { + ArtifactoryWebhookRequest("distribution", "distribute_failed", DistributionFailedEventJSON(), t) +} + +func TestDestinationReceivedEvent(t *testing.T) { + ArtifactoryWebhookRequest("destination", "received", DestinationReceivedEventJSON(), t) +} + +func TestDestinationDeletedStartedEvent(t *testing.T) { + ArtifactoryWebhookRequest("destination", "delete_started", DestinationDeleteStartedEventJSON(), t) +} + +func TestDestinationDeletedCompletedEvent(t *testing.T) { + ArtifactoryWebhookRequest("destination", "delete_completed", DestinationDeleteCompletedEventJSON(), t) +} + +func TestDestinationDeleteFailedEvent(t *testing.T) { + ArtifactoryWebhookRequest("destination", "delete_failed", DestinationDeleteFailedEventJSON(), t) +} + +func TestEventWithSignatureSuccess(t *testing.T) { + ArtifactoryWebhookRequestWithSignature("watch", ArtifactDeployedEventJSON(), t, 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") + } +} diff --git a/plugins/inputs/webhooks/webhooks.go b/plugins/inputs/webhooks/webhooks.go index 5f74964361f3c..c1167565948ad 100644 --- a/plugins/inputs/webhooks/webhooks.go +++ b/plugins/inputs/webhooks/webhooks.go @@ -10,6 +10,7 @@ import ( "github.com/influxdata/telegraf" "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" @@ -29,12 +30,13 @@ func init() { type Webhooks struct { ServiceAddress string `toml:"service_address"` - Github *github.GithubWebhook `toml:"github"` - Filestack *filestack.FilestackWebhook `toml:"filestack"` - Mandrill *mandrill.MandrillWebhook `toml:"mandrill"` - Rollbar *rollbar.RollbarWebhook `toml:"rollbar"` - Papertrail *papertrail.PapertrailWebhook `toml:"papertrail"` - Particle *particle.ParticleWebhook `toml:"particle"` + Github *github.GithubWebhook `toml:"github"` + Filestack *filestack.FilestackWebhook `toml:"filestack"` + Mandrill *mandrill.MandrillWebhook `toml:"mandrill"` + Rollbar *rollbar.RollbarWebhook `toml:"rollbar"` + Papertrail *papertrail.PapertrailWebhook `toml:"papertrail"` + Particle *particle.ParticleWebhook `toml:"particle"` + Artifactory *artifactory.ArtifactoryWebhook `toml:"artifactory"` Log telegraf.Logger `toml:"-"` diff --git a/plugins/inputs/webhooks/webhooks_test.go b/plugins/inputs/webhooks/webhooks_test.go index a44e41433383b..fe1c041d8dca8 100644 --- a/plugins/inputs/webhooks/webhooks_test.go +++ b/plugins/inputs/webhooks/webhooks_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/influxdata/telegraf/plugins/inputs/webhooks/artifactory" "github.com/influxdata/telegraf/plugins/inputs/webhooks/github" "github.com/influxdata/telegraf/plugins/inputs/webhooks/papertrail" "github.com/influxdata/telegraf/plugins/inputs/webhooks/particle" @@ -40,4 +41,10 @@ func TestAvailableWebhooks(t *testing.T) { if !reflect.DeepEqual(wb.AvailableWebhooks(), expected) { t.Errorf("expected to be %v.\nGot %v", expected, wb.AvailableWebhooks()) } + + wb.Artifactory = &artifactory.ArtifactoryWebhook{Path: "/artifactory"} + expected = append(expected, wb.Artifactory) + if !reflect.DeepEqual(wb.AvailableWebhooks(), expected) { + t.Errorf("expected to be %v.\nGot %v", expected, wb.AvailableWebhooks()) + } }