diff --git a/alpine/packages/diagnostics/http.go b/alpine/packages/diagnostics/http.go index 0e321ab2f..588b9c28c 100644 --- a/alpine/packages/diagnostics/http.go +++ b/alpine/packages/diagnostics/http.go @@ -3,23 +3,32 @@ package main import ( "archive/tar" "bytes" + "context" + "errors" "fmt" "io" "io/ioutil" "log" + "net" "net/http" + "net/http/httputil" "os" "strconv" "strings" "time" ) +var ( + errDockerPingNotOK = errors.New("Docker /_ping did not return OK") +) + const ( - dockerSock = "/var/run/docker.sock" - lgtm = "LGTM" - httpMagicPort = ":44554" // chosen arbitrarily due to IANA availability -- might change - bucket = "editionsdiagnostics" - sessionIDField = "session" + healthcheckTimeout = 5 * time.Second + dockerSock = "/var/run/docker.sock" + dockerPingOK = "LGTM" + httpMagicPort = ":44554" // chosen arbitrarily due to IANA availability -- might change + bucket = "editionsdiagnostics" + sessionIDField = "session" ) var ( @@ -36,14 +45,67 @@ func init() { // for cloud editions. type HTTPDiagnosticListener struct{} +// UnixSocketRoundTripper provides a way to make HTTP request to Docker socket +// directly. +type UnixSocketRoundTripper struct{} + +// RoundTrip dials the Docker UNIX socket to make a HTTP request. +func (u UnixSocketRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + dial, err := net.Dial("unix", dockerSock) + if err != nil { + return nil, err + } + conn := httputil.NewClientConn(dial, nil) + defer conn.Close() + return conn.Do(req) +} + // Listen starts the HTTPDiagnosticListener and sets up handlers for its endpoints func (h HTTPDiagnosticListener) Listen() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if _, err := os.Stat(dockerSock); os.IsNotExist(err) { - http.Error(w, "Docker socket not found -- daemon is down", http.StatusServiceUnavailable) + client := &http.Client{ + Transport: &UnixSocketRoundTripper{}, + } + + req, err := http.NewRequest(http.MethodGet, "/_ping", nil) + if err != nil { + http.Error(w, "Error creating HTTP request to talk to Docker", http.StatusInternalServerError) return } - if _, err := w.Write([]byte(lgtm)); err != nil { + + ctx, cancel := context.WithTimeout(context.Background(), healthcheckTimeout) + defer cancel() + + req = req.WithContext(ctx) + + errCh := make(chan error) + + go func() { + resp, err := client.Do(req) + if err != nil { + errCh <- err + return + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + errCh <- errDockerPingNotOK + return + } + errCh <- nil + }() + + select { + case err := <-errCh: + if err != nil { + http.Error(w, "Docker daemon ping error", http.StatusInternalServerError) + return + } + case <-ctx.Done(): + http.Error(w, "Docker daemon ping timed out", http.StatusServiceUnavailable) + return + } + + if _, err := w.Write([]byte(dockerPingOK)); err != nil { log.Println("Error writing HTTP success response:", err) return }