feat(config): add unix socket HTTP config loader support (#5200)

Adds support for loading configuration from HTTP endpoints served over Unix domain sockets using the http+unix:// protocol scheme.
pull/5208/head
Yury Kastov 2025-10-05 06:13:47 +03:00 committed by GitHub
parent c0c88f3d73
commit 2f366aed2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 59 additions and 0 deletions

View File

@ -2,6 +2,8 @@ package external
import ( import (
"bytes" "bytes"
"context"
"net"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -18,6 +20,9 @@ import (
func ConfigLoader(arg string) (out io.Reader, err error) { func ConfigLoader(arg string) (out io.Reader, err error) {
var data []byte var data []byte
switch { switch {
case strings.HasPrefix(arg, "http+unix://"):
data, err = FetchUnixSocketHTTPContent(arg)
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"): case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
data, err = FetchHTTPContent(arg) data, err = FetchHTTPContent(arg)
@ -70,6 +75,60 @@ func FetchHTTPContent(target string) ([]byte, error) {
return content, nil return content, nil
} }
// Format: http+unix:///path/to/socket.sock/api/endpoint
func FetchUnixSocketHTTPContent(target string) ([]byte, error) {
path := strings.TrimPrefix(target, "http+unix://")
if !strings.HasPrefix(path, "/") {
return nil, errors.New("unix socket path must be absolute")
}
var socketPath, httpPath string
sockIdx := strings.Index(path, ".sock")
if sockIdx != -1 {
socketPath = path[:sockIdx+5]
httpPath = path[sockIdx+5:]
if httpPath == "" {
httpPath = "/"
}
} else {
return nil, errors.New("cannot determine socket path, socket file should have .sock extension")
}
if _, err := os.Stat(socketPath); err != nil {
return nil, errors.New("socket file not found: ", socketPath).Base(err)
}
client := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "unix", socketPath)
},
},
}
defer client.CloseIdleConnections()
resp, err := client.Get("http://localhost" + httpPath)
if err != nil {
return nil, errors.New("failed to fetch from unix socket: ", socketPath).Base(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, errors.New("unexpected HTTP status code: ", resp.StatusCode)
}
content, err := buf.ReadAllToBytes(resp.Body)
if err != nil {
return nil, errors.New("failed to read response").Base(err)
}
return content, nil
}
func ExtConfigLoader(files []string, reader io.Reader) (io.Reader, error) { func ExtConfigLoader(files []string, reader io.Reader) (io.Reader, error) {
buf, err := ctlcmd.Run(append([]string{"convert"}, files...), reader) buf, err := ctlcmd.Run(append([]string{"convert"}, files...), reader)
if err != nil { if err != nil {