You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

314 lines
7.6 KiB

package lib
import (
"context"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strings"
"syscall"
"time"
"git.edtech.vm.prod-6.cloud.el/fabric/models"
"github.com/araddon/dateparse"
"github.com/segmentio/ksuid"
duration "github.com/xhit/go-str2duration"
)
var (
reDate = regexp.MustCompile(`^(\d{2})[./](\d{2})[./](\d{4})\b`)
reInterval = regexp.MustCompile(`(.+) ([+-]) (\d[\d.wdhms]*[wdhms])$`)
reUTC = regexp.MustCompile(`(?i)\b(?:UTC|GMT)([+-])(\d+)$`)
LocationMSK = time.FixedZone("Europe/Moscow", 3*3600)
)
// ResponseJSON если status не из списка, то вставляем статус - 501 и Descraption из статуса
func ResponseJSON(w http.ResponseWriter, objResponse interface{}, status string, error error, metrics interface{}) (err error) {
if w == nil {
return
}
errMessage := models.RestStatus{}
st, found := models.StatusCode[status]
if found {
errMessage = st
} else {
errMessage = models.StatusCode["NotStatus"]
}
objResp := &models.Response{}
if error != nil {
errMessage.Error = error
}
// Metrics
b1, _ := json.Marshal(metrics)
var metricsR models.Metrics
json.Unmarshal(b1, &metricsR)
if metrics != nil {
objResp.Metrics = metricsR
}
objResp.Status = errMessage
objResp.Data = objResponse
// формируем ответ
out, err := json.Marshal(objResp)
if err != nil {
out = []byte(fmt.Sprintf("%s", err))
}
//WriteFile("./dump.json", out)
w.WriteHeader(errMessage.Status)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Write(out)
return
}
// RunProcess стартуем сервис из конфига
func RunProcess(path, config, command, mode, dc string) (pid int, err error) {
var cmd *exec.Cmd
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
if config == "" {
return 0, fmt.Errorf("%s", "Configuration file is not found")
}
if command == "" {
command = "start"
}
path = strings.Replace(path, "//", "/", -1)
cmd = exec.Command(path, command, "--config", config, "--mode", mode, "--dc", dc)
if mode == "debug" {
t := time.Now().Format("2006.01.02-15-04-05")
s := strings.Split(path, sep)
srv := s[len(s)-1]
dirPath := "debug" + sep + srv
err = CreateDir(dirPath, 0777)
if err != nil {
return 0, fmt.Errorf("error create directory for debug-file. path: %s, err: %s", dirPath, err)
}
filePath := "debug" + sep + srv + sep + fmt.Sprint(t) + "_" + UUID()[:6] + ".log"
f, err := os.Create(filePath)
if err != nil {
return 0, fmt.Errorf("error create debug-file. path: %s, err: %s", filePath, err)
}
cmd.Stdout = f
cmd.Stderr = f
}
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
err = cmd.Start()
if err != nil {
err = fmt.Errorf("status: %d, config: %s", cmd.ProcessState.ExitCode(), config)
return 0, err
}
pid = cmd.Process.Pid
// в течение заданного интервала ожидаем завершающий статус запуска
// или выходим если -1 (в процессе)
for {
exitCode := cmd.ProcessState.ExitCode()
timer := time.NewTimer(100 * time.Millisecond)
// успешный запуск
if exitCode == 0 {
timer.Stop()
return
}
// финальный неуспех
if exitCode > 0 {
cancel()
}
select {
case <-timer.C:
timer.Stop()
case <-ctx.Done(): // ожидание завершилось, если -1 - то работает
timer.Stop()
return
}
}
return
}
// RootDir получаем корневую директорию от места где запускаем файл
func RootDir() (rootDir string, err error) {
file, err := filepath.Abs(os.Args[0])
if err != nil {
return
}
rootDir = path.Dir(file)
if err != nil {
fmt.Println("Error calculation RootDir. File: ", file, "; Error: ", err)
}
return
}
func Hash(str string) (result string) {
h := sha1.New()
h.Write([]byte(str))
result = hex.EncodeToString(h.Sum(nil))
return
}
func PanicOnErr(err error) {
if err != nil {
fmt.Println("Error: ", err)
panic(err)
}
}
func UUID() (result string) {
return ksuid.New().String()
}
// RemoveElementFromData удаляем элемент из слайса
func RemoveElementFromData(p *models.ResponseData, i int) bool {
if i < len(p.Data) {
p.Data = append(p.Data[:i], p.Data[i+1:]...)
} else {
//log.Warning("Error! Position invalid (", i, ")")
return false
}
return true
}
// JsonEscape экранируем "
// fmt.Println(jsonEscape(`dog "fish" cat`))
// output: dog \"fish\" cat
func JsonEscape(i string) string {
b, err := json.Marshal(i)
if err != nil {
panic(err)
}
s := string(b)
return s[1 : len(s)-1]
}
// SearchConfigDir получаем путь до искомой конфигурации от переданной директории
func SearchConfig(projectDir, configuration string) (configPath string, err error) {
var nextPath string
directory, err := os.Open(projectDir)
if err != nil {
return "", err
}
defer directory.Close()
objects, err := directory.Readdir(-1)
if err != nil {
return "", err
}
// пробегаем текущую папку и считаем совпадание признаков
for _, obj := range objects {
nextPath = projectDir + sep + obj.Name()
if obj.IsDir() {
dirName := obj.Name()
// не входим в скрытые папки
if dirName[:1] != "." {
configPath, err = SearchConfig(nextPath, configuration)
if configPath != "" {
return configPath, err // поднимает результат наверх
}
}
} else {
if !strings.Contains(nextPath, "/.") {
// проверяем только файлы конфигурации (игнорируем .json)
if strings.Contains(obj.Name(), configuration+".cfg") {
return nextPath, err
}
}
}
}
return configPath, err
}
// TimeParse парсит дату-время из любого формата.
// Если в строке не передана временна́я зона, то парсится как UTC.
//
// Если вторым параметром передать true, то полученное время скастится в UTC.
//
// Можно задать интервалы, которые надо добавить/вычесть, знак операции при этом отбивается пробелами.
func TimeParse(str string, toUTC bool) (res time.Time, err error) {
var (
signs, intervals []string
dur time.Duration
)
// извлекаем интервал из строки при наличии
for {
if reInterval.MatchString(str) {
signs = append(signs, reInterval.ReplaceAllString(str, "$2"))
intervals = append(intervals, reInterval.ReplaceAllString(str, "$3"))
str = reInterval.ReplaceAllString(str, "$1")
} else {
break
}
}
// приводим дату к стандартному формату
str = reDate.ReplaceAllString(str, "$3-$2-$1")
// делаем часовой пояс понятнее парсеру
if reUTC.MatchString(str) {
if zone := reUTC.FindStringSubmatch(str); len(zone) >= 2 && len(zone[2]) < 4 {
shift := zone[2]
if len(shift) == 1 {
shift = "0" + shift
}
if len(shift) == 2 {
shift += "00"
}
str = reUTC.ReplaceAllString(str, zone[1]+shift)
}
}
res, err = dateparse.ParseAny(str)
if err != nil {
return
}
// смещаем на заданный интервал
for i, interval := range intervals {
dur, err = duration.Str2Duration(interval)
if err != nil {
return
}
if signs[i] == "-" {
dur = -dur
}
res = res.Add(dur)
}
if toUTC {
return res.UTC(), nil
}
return res, nil
}