Skip to content

Commit

Permalink
add basic port_map healthcheck for compose app (#89)
Browse files Browse the repository at this point in the history
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
  • Loading branch information
tigerinus authored Apr 27, 2023
1 parent 4a80794 commit b865fa3
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 0 deletions.
33 changes: 33 additions & 0 deletions api/app_management/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,26 @@ paths:
"500":
$ref: "#/components/responses/ResponseInternalServerError"

/compose/{id}/healthcheck:
get:
summary: Check if the compose app is running healthy
description: |
By default this method simply check if the `port_map` of the compose app returns `200 OK` status code.
Custom health check procedure will be implemented in the future.
operationId: checkComposeAppHealthByID
tags:
- Compose methods
parameters:
- $ref: "#/components/parameters/ComposeAppID"
responses:
"200":
$ref: "#/components/responses/ComposeAppHealthCheckOK"
"404":
$ref: "#/components/responses/ResponseNotFound"
"503":
$ref: "#/components/responses/ResponseServiceUnavailable"

/container/{id}:
patch:
summary: Recreate the container app
Expand All @@ -453,11 +473,15 @@ paths:

/container/{id}/healthcheck:
get:
deprecated: true
summary: Check if the container app is running healthy
description: |
By default this method simply check if the WebUI port of the app returns `200 OK` status code.
Custom health check procedure will be implemented in the future.
> This method works for legacy apps running on CasaOS v0.4.3 and earlier.
> For compose app, use `GET /compose/{id}/healthcheck` instead.
operationId: checkContainerHealthByID
tags:
- Container methods
Expand Down Expand Up @@ -836,6 +860,15 @@ components:
type: string
example: <logs...>

ComposeAppHealthCheckOK:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/BaseResponse"
example:
message: "pong"

ContainerHealthCheckOK:
description: OK
content:
Expand Down
36 changes: 36 additions & 0 deletions route/v2/compose_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,42 @@ func (a *AppManagement) ComposeAppContainers(ctx echo.Context, id codegen.Compos
})
}

func (a *AppManagement) CheckComposeAppHealthByID(ctx echo.Context, id codegen.ComposeAppID) error {
if id == "" {
message := ErrComposeAppIDNotProvided.Error()
return ctx.JSON(http.StatusBadRequest, codegen.ResponseBadRequest{
Message: &message,
})
}

composeApps, err := service.MyService.Compose().List(ctx.Request().Context())
if err != nil {
message := err.Error()
return ctx.JSON(http.StatusInternalServerError, codegen.ResponseInternalServerError{Message: &message})
}

composeApp, ok := composeApps[id]
if !ok {
message := fmt.Sprintf("compose app `%s` not found", id)
return ctx.JSON(http.StatusNotFound, codegen.ResponseNotFound{Message: &message})
}

result, err := composeApp.HealthCheck()
if err != nil {
message := err.Error()
return ctx.JSON(http.StatusServiceUnavailable, codegen.ResponseServiceUnavailable{Message: &message})
}

if !result {
return ctx.JSON(http.StatusServiceUnavailable, codegen.ResponseServiceUnavailable{})
}

message := fmt.Sprintf("compose app `%s` passed the health check", id)
return ctx.JSON(http.StatusOK, codegen.ComposeAppHealthCheckOK{
Message: &message,
})
}

func YAMLfromRequest(ctx echo.Context) ([]byte, error) {
var buf []byte

Expand Down
45 changes: 45 additions & 0 deletions service/compose_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"context"
"fmt"
"io"
"net/http"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/IceWhaleTech/CasaOS-AppManagement/codegen"
"github.com/IceWhaleTech/CasaOS-AppManagement/common"
Expand All @@ -21,6 +23,7 @@ import (
composeCmd "github.com/docker/compose/v2/cmd/compose"
"github.com/docker/compose/v2/cmd/formatter"
"github.com/docker/compose/v2/pkg/api"
"github.com/go-resty/resty/v2"
"github.com/samber/lo"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -693,6 +696,48 @@ func (a *ComposeApp) GetPortsInUse() (*codegen.ComposeAppValidationErrorsPortsIn
return &codegen.ComposeAppValidationErrorsPortsInUse{PortsInUse: &portsInUse}, nil
}

func (a *ComposeApp) HealthCheck() (bool, error) {
storeInfo, err := a.StoreInfo(false)
if err != nil {
return false, err
}

scheme := "http"
if storeInfo.Scheme != nil {
scheme = string(*storeInfo.Scheme)
}

hostname := common.Localhost
if storeInfo.Hostname != nil {
hostname = *storeInfo.Hostname
}

url := fmt.Sprintf(
"%s://%s:%s/%s",
scheme,
hostname,
storeInfo.PortMap,
strings.TrimLeft(storeInfo.Index, "/"),
)

logger.Info("checking compose app health at the specified web port...", zap.String("name", a.Name), zap.Any("url", url))

client := resty.New()
client.SetTimeout(30 * time.Second)
client.SetHeader("Accept", "text/html")
response, err := client.R().Get(url)
if err != nil {
logger.Error("failed to check container health", zap.Error(err), zap.String("name", a.Name))
return false, err
}
if response.StatusCode() == http.StatusOK || response.StatusCode() == http.StatusUnauthorized {
return true, nil
}

logger.Error("compose app health check failed at the specified web port", zap.Any("name", a.Name), zap.Any("url", url), zap.String("status", fmt.Sprint(response.StatusCode())))
return false, nil
}

func LoadComposeAppFromConfigFile(appID string, configFile string) (*ComposeApp, error) {
options := composeCmd.ProjectOptions{
ProjectDir: filepath.Dir(configFile),
Expand Down

0 comments on commit b865fa3

Please sign in to comment.