Добавить установщик, проверку версий и инструкцию деплоя на сервер.
Интерактивная настройка домена и БД, эндпоинты /health и /version, скрипты install/check для Linux и Windows. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user