diff --git a/docs/LICENSE_OF_DEPENDENCIES.md b/docs/LICENSE_OF_DEPENDENCIES.md index c09576771..2bc9233fa 100644 --- a/docs/LICENSE_OF_DEPENDENCIES.md +++ b/docs/LICENSE_OF_DEPENDENCIES.md @@ -281,6 +281,7 @@ following works: - github.com/opentracing/opentracing-go [Apache License 2.0](https://github.com/opentracing/opentracing-go/blob/master/LICENSE) - github.com/p4lang/p4runtime [Apache License 2.0](https://github.com/p4lang/p4runtime/blob/main/LICENSE) - github.com/pborman/ansi [BSD 3-Clause "New" or "Revised" License](https://github.com/pborman/ansi/blob/master/LICENSE) +- github.com/peterbourgon/unixtransport [Apache License 2.0](https://github.com/peterbourgon/unixtransport/blob/main/LICENSE) - github.com/philhofer/fwd [MIT License](https://github.com/philhofer/fwd/blob/master/LICENSE.md) - github.com/pierrec/lz4 [BSD 3-Clause "New" or "Revised" License](https://github.com/pierrec/lz4/blob/master/LICENSE) - github.com/pion/dtls [MIT License](https://github.com/pion/dtls/blob/master/LICENSES/MIT.txt) diff --git a/go.mod b/go.mod index f503f69c7..da08b89a9 100644 --- a/go.mod +++ b/go.mod @@ -147,6 +147,7 @@ require ( github.com/openzipkin/zipkin-go v0.4.1 github.com/p4lang/p4runtime v1.3.0 github.com/pborman/ansi v1.0.0 + github.com/peterbourgon/unixtransport v0.0.3 github.com/pion/dtls/v2 v2.2.7 github.com/prometheus-community/pro-bing v0.3.0 github.com/prometheus/client_golang v1.16.0 diff --git a/go.sum b/go.sum index c6fea2897..8adb372c3 100644 --- a/go.sum +++ b/go.sum @@ -1094,6 +1094,7 @@ github.com/microsoft/go-mssqldb v1.5.0/go.mod h1:lmWsjHD8XX/Txr0f8ZqgbEZSC+BZjmE github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= @@ -1181,6 +1182,7 @@ github.com/nwaples/tacplus v0.0.3/go.mod h1:y5ZA9N5V2JbmwO766S+ET9zuu5FtL1OtdfBC github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= github.com/olivere/elastic v6.2.37+incompatible h1:UfSGJem5czY+x/LqxgeCBgjDn6St+z8OnsCuxwD3L0U= @@ -1252,6 +1254,10 @@ github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/ff/v3 v3.3.1/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= +github.com/peterbourgon/unixtransport v0.0.3 h1:NmGkSb+OOIeN2rNR+fF+F5kymwRy9VignywyDdtRrpc= +github.com/peterbourgon/unixtransport v0.0.3/go.mod h1:o8aUkOCa8W/BIXpi15uKvbSabjtBh0JhSOJGSfoOhAU= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -1927,6 +1933,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= diff --git a/plugins/common/http/config.go b/plugins/common/http/config.go index aad17fd6e..ffd24558b 100644 --- a/plugins/common/http/config.go +++ b/plugins/common/http/config.go @@ -7,6 +7,7 @@ import ( "time" "github.com/benbjohnson/clock" + "github.com/peterbourgon/unixtransport" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" @@ -48,6 +49,9 @@ func (h *HTTPClientConfig) CreateClient(ctx context.Context, log telegraf.Logger MaxIdleConnsPerHost: h.MaxIdleConnsPerHost, } + // Register "http+unix" and "https+unix" protocol handler. + unixtransport.Register(transport) + timeout := h.Timeout if timeout == 0 { timeout = config.Duration(time.Second * 5) diff --git a/plugins/inputs/http/README.md b/plugins/inputs/http/README.md index d1252584c..fa19d71cd 100644 --- a/plugins/inputs/http/README.md +++ b/plugins/inputs/http/README.md @@ -29,9 +29,10 @@ to use them. ```toml @sample.conf # Read formatted metrics from one or more HTTP endpoints [[inputs.http]] - ## One or more URLs from which to read formatted metrics + ## One or more URLs from which to read formatted metrics. urls = [ - "http://localhost/metrics" + "http://localhost/metrics", + "http+unix:///run/user/420/podman/podman.sock:/d/v4.0.0/libpod/pods/json" ] ## HTTP method @@ -105,6 +106,17 @@ to use them. ``` +HTTP requests over Unix domain sockets can be specified via the "http+unix" or +"https+unix" schemes. +Request URLs should have the following form: + +```text +http+unix:///path/to/service.sock:/api/endpoint +``` + +Note: The path to the Unix domain socket and the request endpoint are separated +by a colon (":"). + ## Example Output This example output was taken from [this instructional article][1]. diff --git a/plugins/inputs/http/http_test.go b/plugins/inputs/http/http_test.go index 450e19b14..bd1204110 100644 --- a/plugins/inputs/http/http_test.go +++ b/plugins/inputs/http/http_test.go @@ -4,9 +4,14 @@ import ( "compress/gzip" "fmt" "io" + "math/rand" + "net" "net/http" "net/http/httptest" "net/url" + "os" + "path/filepath" + "strings" "testing" "time" @@ -446,3 +451,74 @@ func TestHTTPWithCSVFormat(t *testing.T) { require.NoError(t, acc.GatherError(plugin.Gather)) testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) } + +const ( + httpOverUnixScheme = "http+unix" +) + +func TestConnectionOverUnixSocket(t *testing.T) { + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/data" { + w.Header().Set("Content-Type", "text/csv") + _, _ = w.Write([]byte(simpleCSVWithHeader)) + } else { + w.WriteHeader(http.StatusNotFound) + } + })) + + unixListenAddr := filepath.Join(os.TempDir(), fmt.Sprintf("httptestserver.%d.sock", rand.Intn(1_000_000))) + t.Cleanup(func() { os.Remove(unixListenAddr) }) + + unixListener, err := net.Listen("unix", unixListenAddr) + require.NoError(t, err) + + ts.Listener = unixListener + ts.Start() + defer ts.Close() + + // NOTE: Remove ":" from windows filepath and replace all "\" with "/". + // This is *required* so that the unix socket path plays well with unixtransport. + replacer := strings.NewReplacer(":", "", "\\", "/") + sockPath := replacer.Replace(unixListenAddr) + + address := fmt.Sprintf("%s://%s:/data", httpOverUnixScheme, sockPath) + plugin := &httpplugin.HTTP{ + URLs: []string{address}, + Log: testutil.Logger{}, + } + + plugin.SetParserFunc(func() (telegraf.Parser, error) { + parser := &csv.Parser{ + MetricName: "metricName", + SkipRows: 3, + ColumnNames: []string{"a", "b", "c"}, + TagColumns: []string{"c"}, + } + err := parser.Init() + return parser, err + }) + + expected := []telegraf.Metric{ + testutil.MustMetric("metricName", + map[string]string{ + "url": address, + "c": "ok", + }, + map[string]interface{}{ + "a": 1.2, + "b": 3.1415, + }, + time.Unix(22000, 0), + ), + } + + var acc testutil.Accumulator + require.NoError(t, plugin.Init()) + require.NoError(t, acc.GatherError(plugin.Gather)) + testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) + + // Run the parser a second time to test for correct stateful handling + acc.ClearMetrics() + require.NoError(t, acc.GatherError(plugin.Gather)) + testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) +} diff --git a/plugins/inputs/http/sample.conf b/plugins/inputs/http/sample.conf index 9cb4bc704..5b79ede24 100644 --- a/plugins/inputs/http/sample.conf +++ b/plugins/inputs/http/sample.conf @@ -1,8 +1,9 @@ # Read formatted metrics from one or more HTTP endpoints [[inputs.http]] - ## One or more URLs from which to read formatted metrics + ## One or more URLs from which to read formatted metrics. urls = [ - "http://localhost/metrics" + "http://localhost/metrics", + "http+unix:///run/user/420/podman/podman.sock:/d/v4.0.0/libpod/pods/json" ] ## HTTP method diff --git a/plugins/inputs/http/sample.conf.in b/plugins/inputs/http/sample.conf.in index 3951d47ea..c481cf54d 100644 --- a/plugins/inputs/http/sample.conf.in +++ b/plugins/inputs/http/sample.conf.in @@ -1,8 +1,9 @@ # Read formatted metrics from one or more HTTP endpoints [[inputs.http]] - ## One or more URLs from which to read formatted metrics + ## One or more URLs from which to read formatted metrics. urls = [ - "http://localhost/metrics" + "http://localhost/metrics", + "http+unix:///run/user/420/podman/podman.sock:/d/v4.0.0/libpod/pods/json" ] ## HTTP method