a3d3721724
Интерактивная настройка домена и БД, эндпоинты /health и /version, скрипты install/check для Linux и Windows. Co-authored-by: Cursor <cursoragent@cursor.com>
177 lines
4.2 KiB
Go
177 lines
4.2 KiB
Go
package check
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"shop/internal/version"
|
|
)
|
|
|
|
type Status string
|
|
|
|
const (
|
|
StatusOK Status = "ok"
|
|
StatusWarn Status = "warn"
|
|
StatusError Status = "error"
|
|
)
|
|
|
|
type Item struct {
|
|
Name string `json:"name"`
|
|
Status Status `json:"status"`
|
|
Detail string `json:"detail"`
|
|
Expected string `json:"expected,omitempty"`
|
|
}
|
|
|
|
type Report struct {
|
|
AppVersion string `json:"app_version"`
|
|
GoVersion string `json:"go_version"`
|
|
Items []Item `json:"checks"`
|
|
}
|
|
|
|
func (r Report) Healthy() bool {
|
|
for _, it := range r.Items {
|
|
if it.Status == StatusError {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func AppInfo() Report {
|
|
return Report{
|
|
AppVersion: version.AppVersion,
|
|
GoVersion: version.GoRuntime(),
|
|
Items: []Item{
|
|
{
|
|
Name: "go_runtime",
|
|
Status: goRuntimeStatus(),
|
|
Detail: version.GoRuntime(),
|
|
Expected: ">=" + version.MinGoVersion,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func goRuntimeStatus() Status {
|
|
v := strings.TrimPrefix(version.GoRuntime(), "go")
|
|
major, minor, ok := parseGoVersion(v)
|
|
if !ok {
|
|
return StatusWarn
|
|
}
|
|
expMajor, expMinor, _ := parseGoVersion(version.MinGoVersion)
|
|
if major > expMajor || (major == expMajor && minor >= expMinor) {
|
|
return StatusOK
|
|
}
|
|
return StatusWarn
|
|
}
|
|
|
|
func parseGoVersion(v string) (major, minor int, ok bool) {
|
|
parts := strings.Split(v, ".")
|
|
if len(parts) < 2 {
|
|
return 0, 0, false
|
|
}
|
|
major, err1 := strconv.Atoi(parts[0])
|
|
minor, err2 := strconv.Atoi(parts[1])
|
|
return major, minor, err1 == nil && err2 == nil
|
|
}
|
|
|
|
func WithDatabase(ctx context.Context, pool *pgxpool.Pool) (Report, error) {
|
|
r := AppInfo()
|
|
dbItems, err := Database(ctx, pool)
|
|
if err != nil {
|
|
return r, err
|
|
}
|
|
r.Items = append(r.Items, dbItems...)
|
|
return r, nil
|
|
}
|
|
|
|
func Database(ctx context.Context, pool *pgxpool.Pool) ([]Item, error) {
|
|
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
|
|
var pgVersion string
|
|
if err := pool.QueryRow(ctx, `SHOW server_version`).Scan(&pgVersion); err != nil {
|
|
return []Item{{
|
|
Name: "database",
|
|
Status: StatusError,
|
|
Detail: err.Error(),
|
|
}}, nil
|
|
}
|
|
|
|
major, ok := postgresMajor(pgVersion)
|
|
pgStatus := StatusOK
|
|
detail := pgVersion
|
|
expected := fmt.Sprintf("%d.x", version.ExpectedPostgresMajor)
|
|
|
|
if !ok {
|
|
pgStatus = StatusWarn
|
|
detail = pgVersion + " (не удалось определить major)"
|
|
} else if major != version.ExpectedPostgresMajor {
|
|
pgStatus = StatusWarn
|
|
detail = fmt.Sprintf("%s (ожидается PostgreSQL %d)", pgVersion, version.ExpectedPostgresMajor)
|
|
}
|
|
|
|
return []Item{
|
|
{Name: "database", Status: StatusOK, Detail: "подключено"},
|
|
{Name: "postgresql", Status: pgStatus, Detail: detail, Expected: expected},
|
|
}, nil
|
|
}
|
|
|
|
var pgMajorRe = regexp.MustCompile(`^(\d+)`)
|
|
|
|
func postgresMajor(v string) (int, bool) {
|
|
m := pgMajorRe.FindStringSubmatch(strings.TrimSpace(v))
|
|
if len(m) < 2 {
|
|
return 0, false
|
|
}
|
|
n, err := strconv.Atoi(m[1])
|
|
return n, err == nil
|
|
}
|
|
|
|
func ToolVersions(ctx context.Context) []Item {
|
|
var items []Item
|
|
if out, err := run(ctx, "docker", "version", "--format", "{{.Server.Version}}"); err == nil {
|
|
items = append(items, Item{Name: "docker", Status: StatusOK, Detail: strings.TrimSpace(out)})
|
|
} else {
|
|
items = append(items, Item{Name: "docker", Status: StatusWarn, Detail: "не найден"})
|
|
}
|
|
if out, err := run(ctx, "docker", "compose", "version", "--short"); err == nil {
|
|
items = append(items, Item{Name: "docker_compose", Status: StatusOK, Detail: strings.TrimSpace(out)})
|
|
} else {
|
|
items = append(items, Item{Name: "docker_compose", Status: StatusWarn, Detail: "не найден"})
|
|
}
|
|
return items
|
|
}
|
|
|
|
func run(ctx context.Context, name string, args ...string) (string, error) {
|
|
ctx, cancel := context.WithTimeout(ctx, 8*time.Second)
|
|
defer cancel()
|
|
cmd := exec.CommandContext(ctx, name, args...)
|
|
out, err := cmd.Output()
|
|
return string(out), err
|
|
}
|
|
|
|
func Merge(reports ...Report) Report {
|
|
if len(reports) == 0 {
|
|
return AppInfo()
|
|
}
|
|
out := reports[0]
|
|
for _, r := range reports[1:] {
|
|
if out.AppVersion == "" {
|
|
out.AppVersion = r.AppVersion
|
|
}
|
|
if out.GoVersion == "" {
|
|
out.GoVersion = r.GoVersion
|
|
}
|
|
out.Items = append(out.Items, r.Items...)
|
|
}
|
|
return out
|
|
}
|