From 5a15d606a76c08d98e17937c39321d0ceee5da1d Mon Sep 17 00:00:00 2001 From: DazWilkin Date: Mon, 1 Aug 2016 18:53:32 -0700 Subject: [PATCH] Add Dockerhub Webhook Plugin --- plugins/inputs/webhooks/dockerhub/README.md | 41 ++++++++++ .../webhooks/dockerhub/dockerhub_webhooks.go | 50 ++++++++++++ .../dockerhub/dockerhub_webhooks_mocks.go | 65 +++++++++++++++ .../dockerhub/dockerhub_webhooks_models.go | 81 +++++++++++++++++++ .../dockerhub/dockerhub_webhooks_test.go | 28 +++++++ plugins/inputs/webhooks/webhooks.go | 16 ++-- plugins/inputs/webhooks/webhooks_test.go | 10 +-- 7 files changed, 278 insertions(+), 13 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/plugins/inputs/webhooks/dockerhub/README.md b/plugins/inputs/webhooks/dockerhub/README.md new file mode 100644 index 000000000..87c9f934f --- /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://nothing) + +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..80af227b5 --- /dev/null +++ b/plugins/inputs/webhooks/dockerhub/dockerhub_webhooks_mocks.go @@ -0,0 +1,65 @@ +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" + +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": "dazwilkin", + "owner": %s, + "repo_name": "dazwilkin/testwebhook", + "repo_url": %s, + "star_count": 0, + "status": "Active" +} +}`, + registry, + RandStringBytes(64), + RandStringBytes(64), + time.Now().Unix(), + dockerid, + time.Now().Unix(), + dockerid, + fmt.Sprintf("%s/%s", dockerid, imagename), + fmt.Sprintf("%s/u/%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 f433f6f38..9fbb9ab48 100644 --- a/plugins/inputs/webhooks/webhooks.go +++ b/plugins/inputs/webhooks/webhooks.go @@ -10,9 +10,9 @@ 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/particle" "github.com/influxdata/telegraf/plugins/inputs/webhooks/rollbar" ) @@ -27,10 +27,10 @@ func init() { type Webhooks struct { ServiceAddress string - Github *github.GithubWebhook - Mandrill *mandrill.MandrillWebhook - Particle *particle.ParticleWebhook - Rollbar *rollbar.RollbarWebhook + Dockerhub *dockerhub.DockerhubWebhook + Github *github.GithubWebhook + Mandrill *mandrill.MandrillWebhook + Rollbar *rollbar.RollbarWebhook } func NewWebhooks() *Webhooks { @@ -42,15 +42,15 @@ 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" [inputs.webhooks.mandrill] path = "/mandrill" - [inputs.webhooks.particle] - path = "/particle" - [inputs.webhooks.rollbar] path = "/rollbar" ` diff --git a/plugins/inputs/webhooks/webhooks_test.go b/plugins/inputs/webhooks/webhooks_test.go index 5aef734c6..165ca7af7 100644 --- a/plugins/inputs/webhooks/webhooks_test.go +++ b/plugins/inputs/webhooks/webhooks_test.go @@ -4,8 +4,8 @@ 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/particle" "github.com/influxdata/telegraf/plugins/inputs/webhooks/rollbar" ) @@ -16,14 +16,14 @@ func TestAvailableWebhooks(t *testing.T) { t.Errorf("expected to %v.\nGot %v", expected, wb.AvailableWebhooks()) } - wb.Github = &github.GithubWebhook{Path: "/github"} - expected = append(expected, wb.Github) + 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.Particle = &particle.ParticleWebhook{Path: "/particle"} - expected = append(expected, wb.Particle) + wb.Github = &github.GithubWebhook{Path: "/github"} + expected = append(expected, wb.Github) if !reflect.DeepEqual(wb.AvailableWebhooks(), expected) { t.Errorf("expected to be %v.\nGot %v", expected, wb.AvailableWebhooks()) }