Browse Source

add a health check command

Useful in restricted environments where commands like curl and such
are not available.

Fixes #1129

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Nicola Murino 2 years ago
parent
commit
c8d94f0a27

+ 120 - 0
internal/cmd/ping.go

@@ -0,0 +1,120 @@
+// Copyright (C) 2019-2023 Nicola Murino
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+package cmd
+
+import (
+	"fmt"
+	"net/http"
+	"os"
+
+	"github.com/rs/zerolog"
+	"github.com/spf13/cobra"
+
+	"github.com/drakkan/sftpgo/v2/internal/config"
+	"github.com/drakkan/sftpgo/v2/internal/httpclient"
+	"github.com/drakkan/sftpgo/v2/internal/httpd"
+	"github.com/drakkan/sftpgo/v2/internal/logger"
+	"github.com/drakkan/sftpgo/v2/internal/util"
+)
+
+func getHealthzURLFromBindings(bindings []httpd.Binding) string {
+	for _, b := range bindings {
+		if b.Port > 0 && b.IsValid() {
+			var url string
+			if b.EnableHTTPS {
+				url = "https://"
+			} else {
+				url = "http://"
+			}
+			if b.Address == "" {
+				url += "127.0.0.1"
+			} else {
+				url += b.Address
+			}
+			url += fmt.Sprintf(":%d", b.Port)
+			url += "/healthz"
+			return url
+		}
+	}
+	return ""
+}
+
+var (
+	pingCmd = &cobra.Command{
+		Use:   "ping",
+		Short: "Issues an health check to SFTPGo",
+		Long: `This command is only useful in environments where system commands like
+"curl", "wget" and similar are not available.
+Checks over UNIX domain sockets are not supported`,
+		Run: func(_ *cobra.Command, _ []string) {
+			logger.DisableLogger()
+			logger.EnableConsoleLogger(zerolog.DebugLevel)
+			configDir = util.CleanDirInput(configDir)
+			err := config.LoadConfig(configDir, configFile)
+			if err != nil {
+				logger.WarnToConsole("Unable to load configuration: %v", err)
+				os.Exit(1)
+			}
+			httpConfig := config.GetHTTPConfig()
+			err = httpConfig.Initialize(configDir)
+			if err != nil {
+				logger.ErrorToConsole("error initializing http client: %v", err)
+				os.Exit(1)
+			}
+			telemetryConfig := config.GetTelemetryConfig()
+			var url string
+			if telemetryConfig.BindPort > 0 {
+				if telemetryConfig.CertificateFile != "" && telemetryConfig.CertificateKeyFile != "" {
+					url += "https://"
+				} else {
+					url += "http://"
+				}
+				if telemetryConfig.BindAddress == "" {
+					url += "127.0.0.1"
+				} else {
+					url += telemetryConfig.BindAddress
+				}
+				url += fmt.Sprintf(":%d", telemetryConfig.BindPort)
+				url += "/healthz"
+			}
+			if url == "" {
+				httpdConfig := config.GetHTTPDConfig()
+				url = getHealthzURLFromBindings(httpdConfig.Bindings)
+			}
+			if url == "" {
+				logger.ErrorToConsole("no suitable configuration found, please enable the telemetry server or REST API over HTTP/S")
+				os.Exit(1)
+			}
+
+			logger.DebugToConsole("Health Check URL %q", url)
+			resp, err := httpclient.RetryableGet(url)
+			if err != nil {
+				logger.ErrorToConsole("Unable to connect to SFTPGo: %v", err)
+				os.Exit(1)
+			}
+			defer resp.Body.Close()
+			if resp.StatusCode != http.StatusOK {
+				logger.ErrorToConsole("Unexpected status code %d", resp.StatusCode)
+				os.Exit(1)
+			}
+			logger.InfoToConsole("OK")
+		},
+	}
+)
+
+func init() {
+	addConfigFlags(pingCmd)
+	rootCmd.AddCommand(pingCmd)
+}

+ 1 - 1
internal/cmd/resetprovider.go

@@ -45,7 +45,7 @@ Please take a look at the usage below to customize the options.`,
 			configDir = util.CleanDirInput(configDir)
 			err := config.LoadConfig(configDir, configFile)
 			if err != nil {
-				logger.WarnToConsole("Unable to initialize data provider, config load error: %v", err)
+				logger.WarnToConsole("Unable to load configuration: %v", err)
 				os.Exit(1)
 			}
 			kmsConfig := config.GetKMSConfig()

+ 1 - 1
internal/cmd/resetpwd.go

@@ -48,7 +48,7 @@ Please take a look at the usage below to customize the options.`,
 			configDir = util.CleanDirInput(configDir)
 			err := config.LoadConfig(configDir, configFile)
 			if err != nil {
-				logger.WarnToConsole("Unable to initialize data provider, config load error: %v", err)
+				logger.WarnToConsole("Unable to load configuration: %v", err)
 				os.Exit(1)
 			}
 			kmsConfig := config.GetKMSConfig()

+ 1 - 1
internal/cmd/revertprovider.go

@@ -47,7 +47,7 @@ Please take a look at the usage below to customize the options.`,
 			configDir = util.CleanDirInput(configDir)
 			err := config.LoadConfig(configDir, configFile)
 			if err != nil {
-				logger.WarnToConsole("Unable to initialize data provider, config load error: %v", err)
+				logger.WarnToConsole("Unable to load configuration: %v", err)
 				os.Exit(1)
 			}
 			kmsConfig := config.GetKMSConfig()

+ 1 - 1
internal/cmd/smtptest.go

@@ -39,7 +39,7 @@ If the SMTP configuration is correct you should receive this email.`,
 			configDir = util.CleanDirInput(configDir)
 			err := config.LoadConfig(configDir, configFile)
 			if err != nil {
-				logger.WarnToConsole("Unable to initialize data provider, config load error: %v", err)
+				logger.WarnToConsole("Unable to load configuration: %v", err)
 				os.Exit(1)
 			}
 			smtpConfig := config.GetSMTPConfig()