diff --git a/common/sanitize/sanitize.go b/common/sanitize/sanitize.go new file mode 100644 index 0000000000..bd12bf8c9e --- /dev/null +++ b/common/sanitize/sanitize.go @@ -0,0 +1,39 @@ +package sanitize + +import ( + "fmt" + "log" + "net" + "net/url" + "strings" +) + +// URL returns a function that sanitizes a URL string. It lets underspecified +// strings to be converted to usable URLs via some default arguments. +func URL(scheme string, port int, path string) func(string) string { + if scheme == "" { + scheme = "http://" + } + return func(s string) string { + if s == "" { + return s // can't do much here + } + if !strings.HasPrefix(s, "http") { + s = scheme + s + } + u, err := url.Parse(s) + if err != nil { + log.Printf("%q: %v", s, err) + return s // oh well + } + if port > 0 { + if _, _, err = net.SplitHostPort(u.Host); err != nil { + u.Host += fmt.Sprintf(":%d", port) + } + } + if path != "" && u.Path != path { + u.Path = path + } + return u.String() + } +} diff --git a/common/sanitize/sanitize_test.go b/common/sanitize/sanitize_test.go new file mode 100644 index 0000000000..1d65024881 --- /dev/null +++ b/common/sanitize/sanitize_test.go @@ -0,0 +1,34 @@ +package sanitize_test + +import ( + "testing" + + "github.com/weaveworks/scope/common/sanitize" +) + +func TestSanitizeURL(t *testing.T) { + for _, input := range []struct { + scheme string + port int + path string + input string + want string + }{ + {"", 0, "", "", ""}, + {"", 0, "", "foo", "http://foo"}, + {"", 80, "", "foo", "http://foo:80"}, + {"", 0, "some/path", "foo", "http://foo/some/path"}, + {"", 0, "/some/path", "foo", "http://foo/some/path"}, + {"https://", 0, "", "foo", "https://foo"}, + {"https://", 80, "", "foo", "https://foo:80"}, + {"https://", 0, "some/path", "foo", "https://foo/some/path"}, + {"https://", 0, "", "http://foo", "http://foo"}, // specified scheme beats default... + {"", 9999, "", "foo:80", "http://foo:80"}, // specified port beats default... + {"", 0, "/bar", "foo/baz", "http://foo/bar"}, // ...but default path beats specified! + } { + if want, have := input.want, sanitize.URL(input.scheme, input.port, input.path)(input.input); want != have { + t.Errorf("sanitize.URL(%q, %d, %q)(%q): want %q, have %q", input.scheme, input.port, input.path, input.input, want, have) + continue + } + } +} diff --git a/experimental/demoprobe/main.go b/experimental/demoprobe/main.go index 55cb91fb61..51c3ac5bf0 100644 --- a/experimental/demoprobe/main.go +++ b/experimental/demoprobe/main.go @@ -23,7 +23,7 @@ func main() { ) flag.Parse() - publisher, err := xfer.NewHTTPPublisher(*publish, "demoprobe", "demoprobe") + _, publisher, err := xfer.NewHTTPPublisher(*publish, "demoprobe", "demoprobe") if err != nil { log.Fatal(err) } diff --git a/experimental/fixprobe/main.go b/experimental/fixprobe/main.go index 1bd1294b6e..bcec4c05ab 100644 --- a/experimental/fixprobe/main.go +++ b/experimental/fixprobe/main.go @@ -34,7 +34,7 @@ func main() { } f.Close() - publisher, err := xfer.NewHTTPPublisher(*publish, "fixprobe", "fixprobe") + _, publisher, err := xfer.NewHTTPPublisher(*publish, "fixprobe", "fixprobe") if err != nil { log.Fatal(err) } diff --git a/probe/main.go b/probe/main.go index eb4495ff96..29225bb5e5 100644 --- a/probe/main.go +++ b/probe/main.go @@ -87,7 +87,7 @@ func main() { } publisherFactory := func(target string) (xfer.Publisher, error) { - publisher, err := xfer.NewHTTPPublisher(target, *token, probeID) + _, publisher, err := xfer.NewHTTPPublisher(target, *token, probeID) if err != nil { return nil, err } @@ -139,10 +139,7 @@ func main() { } if *weaveRouterAddr != "" { - weave, err := overlay.NewWeave(hostID, *weaveRouterAddr) - if err != nil { - log.Fatalf("failed to start Weave tagger: %v", err) - } + weave := overlay.NewWeave(hostID, *weaveRouterAddr) tickers = append(tickers, weave) taggers = append(taggers, weave) reporters = append(reporters, weave) diff --git a/probe/overlay/weave.go b/probe/overlay/weave.go index f1d60d157d..a9e947c6dc 100644 --- a/probe/overlay/weave.go +++ b/probe/overlay/weave.go @@ -5,13 +5,13 @@ import ( "encoding/json" "fmt" "log" - "net" "net/http" - "net/url" "regexp" "strings" "sync" + "github.com/weaveworks/scope/common/sanitize" + "github.com/weaveworks/scope/common/exec" "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/report" @@ -68,15 +68,11 @@ type weaveStatus struct { // NewWeave returns a new Weave tagger based on the Weave router at // address. The address should be an IP or FQDN, no port. -func NewWeave(hostID, weaveRouterAddress string) (*Weave, error) { - s, err := sanitize("http://", 6784, "/report")(weaveRouterAddress) - if err != nil { - return nil, err - } +func NewWeave(hostID, weaveRouterAddress string) *Weave { return &Weave{ - url: s, + url: sanitize.URL("http://", 6784, "/report")(weaveRouterAddress), hostID: hostID, - }, nil + } } // Tick implements Ticker @@ -202,25 +198,3 @@ func (w *Weave) Report() (report.Report, error) { } return r, nil } - -func sanitize(scheme string, port int, path string) func(string) (string, error) { - return func(s string) (string, error) { - if s == "" { - return "", fmt.Errorf("no host") - } - if !strings.HasPrefix(s, "http") { - s = scheme + s - } - u, err := url.Parse(s) - if err != nil { - return "", err - } - if _, _, err = net.SplitHostPort(u.Host); err != nil { - u.Host += fmt.Sprintf(":%d", port) - } - if u.Path != path { - u.Path = path - } - return u.String(), nil - } -} diff --git a/probe/overlay/weave_test.go b/probe/overlay/weave_test.go index bb5f18f6ee..ab29ca7816 100644 --- a/probe/overlay/weave_test.go +++ b/probe/overlay/weave_test.go @@ -25,11 +25,7 @@ func TestWeaveTaggerOverlayTopology(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(mockWeaveRouter)) defer s.Close() - w, err := overlay.NewWeave(mockHostID, s.URL) - if err != nil { - t.Fatal(err) - } - + w := overlay.NewWeave(mockHostID, s.URL) w.Tick() { diff --git a/xfer/publisher.go b/xfer/publisher.go index 759e48a6ec..0a0df75045 100644 --- a/xfer/publisher.go +++ b/xfer/publisher.go @@ -2,13 +2,15 @@ package xfer import ( "bytes" + "encoding/json" "fmt" "log" "net/http" - "net/url" "strings" "sync" "time" + + "github.com/weaveworks/scope/common/sanitize" ) const ( @@ -17,7 +19,7 @@ const ( ) // Publisher is something which can send a buffered set of data somewhere, -// probably to a collector. +// probably to a remote collector. type Publisher interface { Publish(*bytes.Buffer) error Stop() @@ -25,9 +27,9 @@ type Publisher interface { // HTTPPublisher publishes reports by POST to a fixed endpoint. type HTTPPublisher struct { - url string - token string - id string + url string + token string + probeID string } // ScopeProbeIDHeader is the header we use to carry the probe's unique ID. The @@ -37,21 +39,23 @@ type HTTPPublisher struct { 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) +func NewHTTPPublisher(target, token, probeID string) (string, *HTTPPublisher, error) { + targetAPI := sanitize.URL("http://", 0, "/api")(target) + resp, err := http.Get(targetAPI) if err != nil { - return nil, err + return "", nil, err } - if u.Path == "" { - u.Path = "/api/report" + defer resp.Body.Close() + var apiResponse struct { + ID string `json:"id"` } - return &HTTPPublisher{ - url: u.String(), - token: token, - id: id, + if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil { + return "", nil, err + } + return apiResponse.ID, &HTTPPublisher{ + url: sanitize.URL("http://", 0, "/api/report")(target), + token: token, + probeID: probeID, }, nil } @@ -65,9 +69,8 @@ func (p HTTPPublisher) Publish(buf *bytes.Buffer) error { if err != nil { return err } - req.Header.Set("Authorization", AuthorizationHeader(p.token)) - req.Header.Set(ScopeProbeIDHeader, p.id) + req.Header.Set(ScopeProbeIDHeader, p.probeID) req.Header.Set("Content-Encoding", "gzip") // req.Header.Set("Content-Type", "application/binary") // TODO: we should use http.DetectContentType(..) on the gob'ed diff --git a/xfer/publisher_test.go b/xfer/publisher_test.go index 0bef0861e2..787eee7937 100644 --- a/xfer/publisher_test.go +++ b/xfer/publisher_test.go @@ -4,6 +4,7 @@ import ( "bytes" "compress/gzip" "encoding/gob" + "encoding/json" "net/http" "net/http/httptest" "reflect" @@ -27,6 +28,11 @@ func TestHTTPPublisher(t *testing.T) { ) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/api" { + json.NewEncoder(w).Encode(map[string]string{"id": "irrelevant"}) + return + } + if want, have := xfer.AuthorizationHeader(token), r.Header.Get("Authorization"); want != have { t.Errorf("want %q, have %q", want, have) } @@ -61,7 +67,7 @@ func TestHTTPPublisher(t *testing.T) { s := httptest.NewServer(handlers.CompressHandler(handler)) defer s.Close() - p, err := xfer.NewHTTPPublisher(s.URL, token, id) + _, p, err := xfer.NewHTTPPublisher(s.URL, token, id) if err != nil { t.Fatal(err) }