package middleware import ( "errors" "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/hashicorp/go-version" "edge-infra.dev/pkg/lib/fog" "edge-infra.dev/pkg/sds/emergencyaccess/apierror" errorhandler "edge-infra.dev/pkg/sds/emergencyaccess/apierror/handler" "edge-infra.dev/pkg/sds/emergencyaccess/eaconst" ) // Returns a gin HandlerFunc which can be used as the /health endpoint. // When invoked runs all of the supplied check functions. If any check function // fails with an error the endpoint returns a failing status code. If no checks // are supplied the endpoint returns an ok status code. // check functions must not be nil. // // // List of checks to be run during a health check // checks := []func() error{func() error { return nil }} // // // Register endpoint // gin.Any("/health", middleware.HealthCheck(checks...)) func HealthCheck(checks ...func() error) gin.HandlerFunc { return func(ctx *gin.Context) { for _, check := range checks { if err := check(); err != nil { log := fog.FromContext(ctx).WithName("health") log.Error(err, "failed health check") ctx.String(http.StatusServiceUnavailable, "failed health check: %s", err) return } } ctx.String(http.StatusOK, "ok") } } // Looks at the incoming request header for a RCLI-API-VERSION header and compares it // with the server's version. It also adds the server's version to the response // RCLI-API-SERVER-VERSION header. The version is in a major.minor format. // Only the major value needs to be equal between the incoming and server versions. // If they do not match, a user error is returned. // // This middleware functionality is optional - if no RCLI-API-VERSION header // is in the request, it moves on to the next handler. func VerifyAPIVersion(currentAPIVersionStr string) gin.HandlerFunc { // Only comparing the major version for equality currentAPIVersion, err := version.NewVersion(currentAPIVersionStr) if err != nil { panic(fmt.Errorf("unable to parse current API version: %w", err)) } currentMajorAPIVersion := currentAPIVersion.Segments()[0] constraint := fmt.Sprintf("~>%d.0", currentMajorAPIVersion) versionConstraint, err := version.NewConstraint(constraint) if err != nil { panic(fmt.Errorf("unable to parse versionConstraint: %w", err)) } return func(c *gin.Context) { c.Header(eaconst.APIServerVersionKey, currentAPIVersionStr) v := c.Request.Header.Get(eaconst.APIVersionKey) if v == "" { // APIVersion header is optional and should not return error if absent c.Next() return } incomingVersion, err := version.NewVersion(v) if err != nil { errorhandler.ErrorHandler(c, apierror.E(apierror.ErrAPIVersion)) return } if !versionConstraint.Check(incomingVersion) { errorhandler.ErrorHandler( c, apierror.E( apierror.ErrAPIVersion, fmt.Sprintf("The current RemoteCLI version is incompatible with the server version. Please download the appropriate remotecli binary for the environment. Server API Version %s", currentAPIVersion), errors.New("remotecli API version is not compatible with server"), ), ) return } c.Next() } }