From 7d9e2ca05cbc5648dc164fa77b49ec244feac80f Mon Sep 17 00:00:00 2001 From: DazWilkin Date: Sat, 6 Aug 2016 17:09:09 -0700 Subject: [PATCH] Add Docker Hub Webhook Plugin --- README.md | 1 + plugins/inputs/webhooks/README.md | 1 + plugins/inputs/webhooks/dockerhub/README.md | 41 ++++++++++ .../webhooks/dockerhub/dockerhub_webhooks.go | 50 ++++++++++++ .../dockerhub/dockerhub_webhooks_mocks.go | 66 +++++++++++++++ .../dockerhub/dockerhub_webhooks_models.go | 81 +++++++++++++++++++ .../dockerhub/dockerhub_webhooks_test.go | 28 +++++++ plugins/inputs/webhooks/webhooks.go | 11 ++- plugins/inputs/webhooks/webhooks_test.go | 7 ++ 9 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 plugins/inputs/webhooks/dockerhub/README.md create mode 100644 plugins/inputs/webhooks/dockerhub/dockerhub_webhooks.go create mode 100644 plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_mocks.go create mode 100644 plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_models.go create mode 100644 plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_test.go diff --git a/README.md b/README.md index 9d2ee3ce1..5c2b09517 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,7 @@ Telegraf can also collect metrics via the following service plugins: * [kafka_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/kafka_consumer) * [nats_consumer](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/nats_consumer) * [webhooks](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks) + * [dockerhub](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/dockerhub) * [github](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/github) * [mandrill](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/mandrill) * [rollbar](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/webhooks/rollbar) diff --git a/plugins/inputs/webhooks/README.md b/plugins/inputs/webhooks/README.md index 86e6685b8..7d5c6e423 100644 --- a/plugins/inputs/webhooks/README.md +++ b/plugins/inputs/webhooks/README.md @@ -15,6 +15,7 @@ $ sudo service telegraf start ## Available webhooks +- [Dockerhub](dockerhub/) - [Github](github/) - [Mandrill](mandrill/) - [Rollbar](rollbar/) diff --git a/plugins/inputs/webhooks/dockerhub/README.md b/plugins/inputs/webhooks/dockerhub/README.md new file mode 100644 index 000000000..a1736b041 --- /dev/null +++ b/plugins/inputs/webhooks/dockerhub/README.md @@ -0,0 +1,41 @@ +# Docker Hub Webhook + +Docker Hub can be configured to send events to Webhooks. The page describes the JSON format of the events: + +https://docs.docker.com/docker-hub/webhooks/ + +Webhooks are configured by repository: + +https://hub.docker.com/r/[[User]]/[Repository]]/~/settings/webhooks/ + +## Events + +An event is generated by Docker Hub as the result of a docker push to the repository. + +#### [`dockerhub_event`](https://docs.docker.com/docker-hub/webhooks/) + +The measurement is called "dockerhub" + +**Tags:** + +* 'description' = `repository.description` string +* 'name' = `repository.name` string +* 'namespace' = `repository.namespace` string +* 'owner' = `repository.owner` string +* 'repo_name' = `repository.repo_name` string +* 'repo_url' = `repository.repo_url` string +* 'status' = `repository.status` string + +* 'pusher' = `push_data.pusher` string +* 'tag' = `push_data.tag` string + +**Fields:** + +* 'comment_count' = `repository.comment_count` int +* 'date_created' = `repository.date_created` int64 +* 'is_official' = `repository.is_official` bool +* 'is_private' = `repository.is_private` bool +* 'is_trusted' = `repository.is_trusted` bool +* 'star_count' = `repository.stat_count` int + +* 'pushed_at' = `push_data.psuhed_at` int64 diff --git a/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks.go b/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks.go new file mode 100644 index 000000000..a2599f522 --- /dev/null +++ b/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks.go @@ -0,0 +1,50 @@ +package dockerhub + +import ( + "encoding/json" + "io/ioutil" + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/influxdata/telegraf" +) + +type DockerhubWebhook struct { + Path string + acc telegraf.Accumulator +} + +func (dhwh *DockerhubWebhook) Register(router *mux.Router, acc telegraf.Accumulator) { + router.HandleFunc(dhwh.Path, dhwh.eventHandler).Methods("POST") + log.Printf("Started '%s' on %s\n", meas, dhwh.Path) + dhwh.acc = acc +} + +func (dhwh *DockerhubWebhook) eventHandler(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + data, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + e, err := NewEvent(data, &DockerhubEvent{}) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + dh := e.NewMetric() + dhwh.acc.AddFields(meas, dh.Fields(), dh.Tags(), dh.Time()) + w.WriteHeader(http.StatusOK) +} + +func NewEvent(data []byte, event Event) (Event, error) { + err := json.Unmarshal(data, event) + if err != nil { + return nil, err + } + return event, nil +} diff --git a/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_mocks.go b/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_mocks.go new file mode 100644 index 000000000..eee48a858 --- /dev/null +++ b/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_mocks.go @@ -0,0 +1,66 @@ +package dockerhub + +import ( + "fmt" + "math/rand" + "time" +) + +// See https://docs.docker.com/docker-hub/webhooks/ + +const dockerid = "somerandomuser" +const hexBytes = "0123456789abcdef" +const imagename = "testimage" +const registry = "https://registry.hub.docker.com/u/" + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func RandStringBytes(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = hexBytes[rand.Intn(len(hexBytes))] + } + return string(b) +} + +func NewEventJSONEncoded() string { + return fmt.Sprintf(`{ +"callback_url": "%s", +"push_data": { + "images": [ + "%s", + "%s" + ], + "pushed_at": %v, + "pusher": "%s" +}, +"repository": { + "comment_count": 0, + "date_created": %v, + "description": "", + "dockerfile": "", + "is_official": false, + "is_private": true, + "is_trusted": true, + "name": "testhook", + "namespace": "%s", + "owner": "%s", + "repo_name": "%s", + "repo_url": "%s", + "star_count": 0, + "status": "Active" +} +}`, + fmt.Sprintf("%s/%s/%s/%s/", registry, dockerid, imagename, RandStringBytes(64)), + RandStringBytes(64), + RandStringBytes(64), + time.Now().Unix(), + dockerid, + time.Now().Unix(), + dockerid, + dockerid, + fmt.Sprintf("%s/%s", dockerid, imagename), + fmt.Sprintf("%s/%s/%s/", registry, dockerid, imagename)) +} diff --git a/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_models.go b/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_models.go new file mode 100644 index 000000000..0063a57e8 --- /dev/null +++ b/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_models.go @@ -0,0 +1,81 @@ +package dockerhub + +import ( + "fmt" + "log" + "time" + + "github.com/influxdata/telegraf" +) + +const meas = "dockerhub" + +type Event interface { + NewMetric() telegraf.Metric +} + +type PushData struct { + Images []string `json:"images"` + PushedAt int64 `json:"pushed_at"` + Pusher string `json:"pusher"` + Tag string `json:"tag"` +} + +type Repository struct { + CommentCount int `json:"comment_count"` + DateCreated int64 `json:"date_created"` + Description string `json:"description"` + Dockerfile string `json:"dockerfile"` + FullDescription string `json:"full_description"` + IsOfficial bool `json:"is__official"` + IsPrivate bool `json:"is_private"` + IsTrusted bool `json:"is_trusted"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Owner string `json:"owner"` + RepoName string `json:"repo_name"` + RepoURL string `json:"repo_url"` + StarCount int `json:"star_count"` + Status string `json:"status"` +} + +type DockerhubEvent struct { + CallbackURL string `json:"callback_url"` + PushData PushData `json:"push_data"` + Repository Repository `json:"repository"` +} + +func (dhe DockerhubEvent) String() string { + return fmt.Sprintf(`{ + callback_url: %v +}`, + dhe.CallbackURL) +} + +func (dhe DockerhubEvent) NewMetric() telegraf.Metric { + tags := map[string]string{ + "description": dhe.Repository.Description, + "name": dhe.Repository.Name, + "namespace": dhe.Repository.Namespace, + "owner": dhe.Repository.Owner, + "pusher": dhe.PushData.Pusher, + "repo_name": dhe.Repository.RepoName, + "repo_url": dhe.Repository.RepoURL, + "status": dhe.Repository.Status, + "tag": dhe.PushData.Tag, + } + fields := map[string]interface{}{ + "comment_count": dhe.Repository.CommentCount, + "date_created": dhe.Repository.DateCreated, + "is_official": dhe.Repository.IsOfficial, + "is_private": dhe.Repository.IsPrivate, + "is_trusted": dhe.Repository.IsTrusted, + "pushed_at": dhe.PushData.PushedAt, + "star_count": dhe.Repository.StarCount, + } + metric, err := telegraf.NewMetric(meas, tags, fields, time.Unix(dhe.PushData.PushedAt, 0)) + if err != nil { + log.Fatalf("Failed to create %v event", meas) + } + return metric +} diff --git a/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_test.go b/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_test.go new file mode 100644 index 000000000..87a41fdc7 --- /dev/null +++ b/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_test.go @@ -0,0 +1,28 @@ +package dockerhub + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/influxdata/telegraf/testutil" +) + +func DockerhubWebhookRequest(event string, jsonString string, t *testing.T) { + var acc testutil.Accumulator + dhwh := &DockerhubWebhook{Path: "/dockerhub", acc: &acc} + req, _ := http.NewRequest("POST", "/dockerhub", strings.NewReader(jsonString)) + w := httptest.NewRecorder() + dhwh.eventHandler(w, req) + if w.Code != http.StatusOK { + t.Errorf( + "POST"+event+" returned HTTP status code %v.\nExpected %v", + w.Code, + http.StatusOK) + } +} + +func TestNewEvent(t *testing.T) { + DockerhubWebhookRequest("dockerhub_event", NewEventJSONEncoded(), t) +} diff --git a/plugins/inputs/webhooks/webhooks.go b/plugins/inputs/webhooks/webhooks.go index 884435c36..9fbb9ab48 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/dockerhub" "github.com/influxdata/telegraf/plugins/inputs/webhooks/github" "github.com/influxdata/telegraf/plugins/inputs/webhooks/mandrill" "github.com/influxdata/telegraf/plugins/inputs/webhooks/rollbar" @@ -26,9 +27,10 @@ func init() { type Webhooks struct { ServiceAddress string - Github *github.GithubWebhook - Mandrill *mandrill.MandrillWebhook - Rollbar *rollbar.RollbarWebhook + Dockerhub *dockerhub.DockerhubWebhook + Github *github.GithubWebhook + Mandrill *mandrill.MandrillWebhook + Rollbar *rollbar.RollbarWebhook } func NewWebhooks() *Webhooks { @@ -40,6 +42,9 @@ func (wb *Webhooks) SampleConfig() string { ## Address and port to host Webhook listener on service_address = ":1619" + [inputs.webhooks.dockerhub] + path = "/dockerhub" + [inputs.webhooks.github] path = "/github" diff --git a/plugins/inputs/webhooks/webhooks_test.go b/plugins/inputs/webhooks/webhooks_test.go index 85d359e1c..165ca7af7 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/dockerhub" "github.com/influxdata/telegraf/plugins/inputs/webhooks/github" "github.com/influxdata/telegraf/plugins/inputs/webhooks/rollbar" ) @@ -15,6 +16,12 @@ func TestAvailableWebhooks(t *testing.T) { t.Errorf("expected to %v.\nGot %v", expected, wb.AvailableWebhooks()) } + wb.Dockerhub = &dockerhub.DockerhubWebhook{Path: "/dockerhub"} + expected = append(expected, wb.Dockerhub) + if !reflect.DeepEqual(wb.AvailableWebhooks(), expected) { + t.Errorf("expected to be %v.\nGot %v", expected, wb.AvailableWebhooks()) + } + wb.Github = &github.GithubWebhook{Path: "/github"} expected = append(expected, wb.Github) if !reflect.DeepEqual(wb.AvailableWebhooks(), expected) {