diff options
author | Jaime Martinez <jmartinez@gitlab.com> | 2021-10-13 08:46:56 +0300 |
---|---|---|
committer | Jaime Martinez <jmartinez@gitlab.com> | 2021-10-13 08:46:56 +0300 |
commit | b9e48b62307e7b6ea18949a805bb986deac79032 (patch) | |
tree | 56e2b60a92c561aca307aae64f99c3efa08df7ac | |
parent | 247bd7ba2fd9139711218c6a42ed03c551f958d9 (diff) |
feat: add status-address listener609-listen-status-ready
Changelog: added
-rw-r--r-- | app.go | 37 | ||||
-rw-r--r-- | internal/config/config.go | 4 | ||||
-rw-r--r-- | internal/config/flags.go | 1 | ||||
-rw-r--r-- | main.go | 20 | ||||
-rw-r--r-- | test/acceptance/healthcheck_test.go | 44 |
5 files changed, 102 insertions, 4 deletions
@@ -76,7 +76,7 @@ func (a *theApp) ServeTLS(ch *cryptotls.ClientHelloInfo) (*cryptotls.Certificate return nil, nil } -func (a *theApp) healthCheck(w http.ResponseWriter, r *http.Request, https bool) { +func (a *theApp) healthCheck(w http.ResponseWriter, r *http.Request) { if a.isReady() { w.Write([]byte("success\n")) } else { @@ -150,9 +150,7 @@ func (a *theApp) tryAuxiliaryHandlers(w http.ResponseWriter, r *http.Request, ht // healthCheckMiddleware is serving the application status check func (a *theApp) healthCheckMiddleware(handler http.Handler) (http.Handler, error) { - healthCheck := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - a.healthCheck(w, r, request.IsHTTPS(r)) - }) + healthCheck := http.HandlerFunc(a.healthCheck) loggedHealthCheck, err := logging.BasicAccessLogger(healthCheck, a.config.Log.Format, nil) if err != nil { @@ -336,6 +334,10 @@ func (a *theApp) Run() { a.listenMetricsFD(&wg, a.config.ListenMetrics) } + if a.config.ListenStatus != 0 { + a.listenStatusFD(&wg, a.config.ListenStatus, limiter) + } + wg.Wait() } @@ -415,6 +417,33 @@ func (a *theApp) listenMetricsFD(wg *sync.WaitGroup, fd uintptr) { }() } +func (a *theApp) listenStatusFD(wg *sync.WaitGroup, fd uintptr, limiter *netutil.Limiter) { + wg.Add(1) + go func() { + defer wg.Done() + + healthCheck := http.HandlerFunc(a.healthCheck) + + loggedHealthCheck, err := logging.BasicAccessLogger(healthCheck, a.config.Log.Format, nil) + if err != nil { + capturingFatal(err, errortracking.WithField("listener", "status")) + } + + httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == a.config.General.StatusPath { + loggedHealthCheck.ServeHTTP(w, r) + return + } + + httperrors.Serve404(w) + }) + + if err := a.listenAndServe(listenerConfig{fd: fd, handler: httpHandler, limiter: limiter, tlsConfig: nil, isProxyV2: false}); err != nil { + capturingFatal(err, errortracking.WithField("listener", request.SchemeHTTPS)) + } + }() +} + func runApp(config *cfg.Config) { source, err := gitlab.New(&config.GitLab) if err != nil { diff --git a/internal/config/config.go b/internal/config/config.go index 94c22328..24d8a82c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -30,6 +30,7 @@ type Config struct { // ListenMetrics points to a file descriptor of a socket, whose address is // specified by `Config.General.MetricsAddress`. ListenMetrics uintptr + ListenStatus uintptr // These fields contain the raw strings passed for listen-http, // listen-https, listen-proxy and listen-https-proxyv2 settings. It is used @@ -52,6 +53,7 @@ type General struct { RootDir string RootKey []byte StatusPath string + StatusAddress string DisableCrossOriginRequests bool InsecureCiphers bool @@ -178,6 +180,7 @@ func loadConfig() (*Config, error) { RedirectHTTP: *redirectHTTP, RootDir: *pagesRoot, StatusPath: *pagesStatus, + StatusAddress: *statusAddress, DisableCrossOriginRequests: *disableCrossOriginRequests, InsecureCiphers: *insecureCiphers, PropagateCorrelationID: *propagateCorrelationID, @@ -288,6 +291,7 @@ func LogConfig(config *Config) { "root-cert": *pagesRootKey, "root-key": *pagesRootCert, "status_path": config.General.StatusPath, + "status_address": config.General.StatusAddress, "tls-min-version": *tlsMinVersion, "tls-max-version": *tlsMaxVersion, "gitlab-server": config.GitLab.PublicServer, diff --git a/internal/config/flags.go b/internal/config/flags.go index 52b7be18..7e753ee4 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -18,6 +18,7 @@ var ( artifactsServer = flag.String("artifacts-server", "", "API URL to proxy artifact requests to, e.g.: 'https://gitlab.com/api/v4'") artifactsServerTimeout = flag.Int("artifacts-server-timeout", 10, "Timeout (in seconds) for a proxied request to the artifacts server") pagesStatus = flag.String("pages-status", "", "The url path for a status page, e.g., /@status") + statusAddress = flag.String("status-address", "", "The address to listen for status requests") metricsAddress = flag.String("metrics-address", "", "The address to listen on for metrics requests") sentryDSN = flag.String("sentry-dsn", "", "The address for sending sentry crash reporting to") sentryEnvironment = flag.String("sentry-environment", "", "The environment for sentry crash reporting") @@ -80,6 +80,7 @@ func appMain() { for _, cs := range [][]io.Closer{ createAppListeners(config), createMetricsListener(config), + createStatusListener(config), } { defer closeAll(cs) } @@ -176,6 +177,25 @@ func createMetricsListener(config *cfg.Config) []io.Closer { return []io.Closer{l, f} } +// createStatusListener returns net.Listener and *os.File instances. The +// caller must ensure they don't get closed or garbage-collected (which +// implies closing) too soon. +func createStatusListener(config *cfg.Config) []io.Closer { + addr := config.General.StatusAddress + if addr == "" { + return nil + } + + l, f := createSocket(addr) + config.ListenStatus = f.Fd() + + log.WithFields(log.Fields{ + "listener": addr, + }).Debug("Set up status listener") + + return []io.Closer{l, f} +} + func printVersion(showVersion bool, version string) { if showVersion { fmt.Fprintf(os.Stdout, "%s\n", version) diff --git a/test/acceptance/healthcheck_test.go b/test/acceptance/healthcheck_test.go new file mode 100644 index 00000000..fa4b6541 --- /dev/null +++ b/test/acceptance/healthcheck_test.go @@ -0,0 +1,44 @@ +package acceptance_test + +import ( + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHealthCheckListener(t *testing.T) { + statusPath := "/-/readiness" + statusAddress := "127.0.0.1:4100" + + RunPagesProcess(t, + withListeners([]ListenSpec{httpListener}), + withExtraArgument("pages-status", statusPath), + withExtraArgument("status-address", statusAddress), + ) + + tcs := map[string]struct { + path string + expectedStatus int + }{ + "readiness_path": {path: statusPath, expectedStatus: http.StatusOK}, + "another_path": {path: "/another-path", expectedStatus: http.StatusNotFound}, + } + + for tn, tc := range tcs { + t.Run(tn, func(t *testing.T) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s%s", statusAddress, tc.path), nil) + require.NoError(t, err) + + res, err := QuickTimeoutHTTPSClient.Do(req) + require.NoError(t, err) + + require.Equal(t, tc.expectedStatus, res.StatusCode) + + res2, err := GetPageFromListener(t, httpListener, "gitlab-example.com", tc.path) + require.NoError(t, err) + require.Equal(t, tc.expectedStatus, res2.StatusCode, "http listener must match too") + }) + } +} |