mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-15 19:41:02 +00:00
145 lines
3.5 KiB
Go
145 lines
3.5 KiB
Go
package xfer
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/gob"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/weaveworks/scope/report"
|
|
)
|
|
|
|
// Publisher is something which can send a report to a remote collector.
|
|
type Publisher interface {
|
|
Publish(report.Report) error
|
|
}
|
|
|
|
// HTTPPublisher publishes reports by POST to a fixed endpoint.
|
|
type HTTPPublisher struct {
|
|
url string
|
|
token string
|
|
id string
|
|
}
|
|
|
|
// ScopeProbeIDHeader is the header we use to carry the probe's unique ID. The
|
|
// ID is currently set to the probe's hostname. It's designed to deduplicate
|
|
// reports from the same probe to the same receiver, in case the probe is
|
|
// configured to publish to multiple receivers that resolve to the same app.
|
|
const ScopeProbeIDHeader = "X-Scope-Probe-ID"
|
|
|
|
// NewHTTPPublisher returns an HTTPPublisher ready for use.
|
|
func NewHTTPPublisher(target, token, id string) (*HTTPPublisher, error) {
|
|
if !strings.HasPrefix(target, "http") {
|
|
target = "http://" + target
|
|
}
|
|
u, err := url.Parse(target)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if u.Path == "" {
|
|
u.Path = "/api/report"
|
|
}
|
|
return &HTTPPublisher{
|
|
url: u.String(),
|
|
token: token,
|
|
id: id,
|
|
}, nil
|
|
}
|
|
|
|
// Publish publishes the report to the URL.
|
|
func (p HTTPPublisher) Publish(rpt report.Report) error {
|
|
gzbuf := bytes.Buffer{}
|
|
gzwriter := gzip.NewWriter(&gzbuf)
|
|
|
|
if err := gob.NewEncoder(gzwriter).Encode(rpt); err != nil {
|
|
return err
|
|
}
|
|
gzwriter.Close() // otherwise the content won't get flushed to the output stream
|
|
|
|
req, err := http.NewRequest("POST", p.url, &gzbuf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req.Header.Set("Authorization", AuthorizationHeader(p.token))
|
|
req.Header.Set(ScopeProbeIDHeader, p.id)
|
|
req.Header.Set("Content-Encoding", "gzip")
|
|
// req.Header.Set("Content-Type", "application/binary") // TODO: we should use http.DetectContentType(..) on the gob'ed
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf(resp.Status)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AuthorizationHeader returns a value suitable for an HTTP Authorization
|
|
// header, based on the passed token string.
|
|
func AuthorizationHeader(token string) string {
|
|
return fmt.Sprintf("Scope-Probe token=%s", token)
|
|
}
|
|
|
|
// MultiPublisher implements Publisher over a set of publishers.
|
|
type MultiPublisher struct {
|
|
mtx sync.RWMutex
|
|
factory func(string) (Publisher, error)
|
|
m map[string]Publisher
|
|
}
|
|
|
|
// NewMultiPublisher returns a new MultiPublisher ready for use. The factory
|
|
// should be e.g. NewHTTPPublisher, except you need to curry it over the
|
|
// probe token.
|
|
func NewMultiPublisher(factory func(string) (Publisher, error)) *MultiPublisher {
|
|
return &MultiPublisher{
|
|
factory: factory,
|
|
m: map[string]Publisher{},
|
|
}
|
|
}
|
|
|
|
// Add allows additional targets to be added dynamically. It will dedupe
|
|
// identical targets. TODO we have no good mechanism to remove.
|
|
func (p *MultiPublisher) Add(target string) {
|
|
p.mtx.Lock()
|
|
defer p.mtx.Unlock()
|
|
|
|
if _, ok := p.m[target]; ok {
|
|
return
|
|
}
|
|
|
|
publisher, err := p.factory(target)
|
|
if err != nil {
|
|
log.Printf("multi-publisher: %v", err)
|
|
return
|
|
}
|
|
|
|
p.m[target] = publisher
|
|
}
|
|
|
|
// Publish implements Publisher by emitting the report to all publishers.
|
|
func (p *MultiPublisher) Publish(rpt report.Report) error {
|
|
p.mtx.RLock()
|
|
defer p.mtx.RUnlock()
|
|
|
|
var errs []string
|
|
for _, publisher := range p.m {
|
|
if err := publisher.Publish(rpt); err != nil {
|
|
errs = append(errs, err.Error())
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return fmt.Errorf(strings.Join(errs, "; "))
|
|
}
|
|
return nil
|
|
}
|