From c8d94f0a276a7050081ce02553b42710f7beb8e6 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Mon, 16 Jan 2023 18:54:42 +0100 Subject: [PATCH] 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 --- internal/cmd/ping.go | 120 +++++++++++++++++++++++++++++++++ internal/cmd/resetprovider.go | 2 +- internal/cmd/resetpwd.go | 2 +- internal/cmd/revertprovider.go | 2 +- internal/cmd/smtptest.go | 2 +- 5 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 internal/cmd/ping.go diff --git a/internal/cmd/ping.go b/internal/cmd/ping.go new file mode 100644 index 00000000..98742aba --- /dev/null +++ b/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 . + +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) +} diff --git a/internal/cmd/resetprovider.go b/internal/cmd/resetprovider.go index b151c578..6a923f3d 100644 --- a/internal/cmd/resetprovider.go +++ b/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() diff --git a/internal/cmd/resetpwd.go b/internal/cmd/resetpwd.go index 0183c477..7cd3975f 100644 --- a/internal/cmd/resetpwd.go +++ b/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() diff --git a/internal/cmd/revertprovider.go b/internal/cmd/revertprovider.go index 5e11e4c9..2a1f3d20 100644 --- a/internal/cmd/revertprovider.go +++ b/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() diff --git a/internal/cmd/smtptest.go b/internal/cmd/smtptest.go index bc66b1d8..6f0290a7 100644 --- a/internal/cmd/smtptest.go +++ b/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()