mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-16 01:22:00 +00:00
236 lines
4.0 KiB
Go
236 lines
4.0 KiB
Go
package utils
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strconv"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
|
|
)
|
|
|
|
func ReadPidFile(name string) (int, error) {
|
|
|
|
pidPath := pidPath()
|
|
|
|
pidFile, err := os.ReadFile(filepath.Join(pidPath, fmt.Sprintf("%s.pid", name)))
|
|
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Strip whitespace and /r or /n
|
|
pidFile = bytes.TrimSpace(pidFile)
|
|
|
|
return strconv.Atoi(string(pidFile))
|
|
}
|
|
|
|
func GeneratePidFile(name string) (string, error) {
|
|
|
|
if name == "" {
|
|
return "", errors.New("name is required")
|
|
}
|
|
|
|
pidPath := pidPath()
|
|
|
|
if pidPath == "" {
|
|
return "", errors.New("pid path is empty")
|
|
}
|
|
|
|
return filepath.Join(pidPath, fmt.Sprintf("%s.pid", name)), nil
|
|
}
|
|
|
|
func GenerateSocketFile(name string) (string, error) {
|
|
|
|
if name == "" {
|
|
return "", errors.New("name is required")
|
|
}
|
|
|
|
pidPath := pidPath()
|
|
|
|
if pidPath == "" {
|
|
return "", errors.New("pid path is empty")
|
|
}
|
|
|
|
return filepath.Join(pidPath, fmt.Sprintf("%s.sock", name)), nil
|
|
}
|
|
|
|
func WritePidFile(name string, pid int) error {
|
|
|
|
// Write PID to file, check XDG, otherwise user home directory ~/hive/
|
|
pidFilename, err := GeneratePidFile(name)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pidFile, err := os.Create(pidFilename)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer pidFile.Close()
|
|
pidFile.WriteString(fmt.Sprintf("%d", pid))
|
|
|
|
return nil
|
|
}
|
|
|
|
func RemovePidFile(serviceName string) error {
|
|
|
|
pidPath := pidPath()
|
|
|
|
os.Remove(filepath.Join(pidPath, fmt.Sprintf("%s.pid", serviceName)))
|
|
|
|
return nil
|
|
}
|
|
|
|
func pidPath() string {
|
|
var pidPath string
|
|
|
|
// CHeck if a directory exists
|
|
|
|
if os.Getenv("XDG_RUNTIME_DIR") != "" {
|
|
pidPath = os.Getenv("XDG_RUNTIME_DIR")
|
|
} else if dirExists(fmt.Sprintf("%s/%s", os.Getenv("HOME"), "hive")) {
|
|
pidPath = filepath.Join(os.Getenv("HOME"), "hive")
|
|
} else {
|
|
pidPath = os.TempDir()
|
|
}
|
|
|
|
return pidPath
|
|
}
|
|
|
|
func StopProcess(serviceName string) error {
|
|
pid, err := ReadPidFile(serviceName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = KillProcess(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Remove PID file
|
|
RemovePidFile(serviceName)
|
|
|
|
return nil
|
|
}
|
|
|
|
func KillProcess(pid int) error {
|
|
|
|
process, err := os.FindProcess(pid)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Send SIGTERM first (graceful)
|
|
err = process.Signal(syscall.SIGTERM)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check process terminated
|
|
|
|
checks := 0
|
|
for {
|
|
time.Sleep(1 * time.Second)
|
|
process, err = os.FindProcess(pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = process.Signal(syscall.Signal(0))
|
|
|
|
if err != nil {
|
|
// Process terminated, break
|
|
break
|
|
}
|
|
|
|
checks++
|
|
|
|
// If process is still running after 3 checks, force kill
|
|
if checks > 3 {
|
|
err = process.Kill() // SIGKILL
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func WaitForPidFileRemoval(instanceID string, timeout time.Duration) error {
|
|
timeoutCh := time.After(timeout)
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-timeoutCh:
|
|
return fmt.Errorf("timeout waiting for PID file to be removed for instance %s", instanceID)
|
|
case <-ticker.C:
|
|
_, err := ReadPidFile(instanceID)
|
|
if err != nil {
|
|
// PID file no longer exists
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func dirExists(path string) bool {
|
|
info, err := os.Stat(path)
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return info.IsDir()
|
|
}
|
|
|
|
// Convert interface to XML
|
|
func MarshalToXML(payload interface{}) (bytes.Buffer, error) {
|
|
|
|
var buf bytes.Buffer
|
|
enc := xml.NewEncoder(&buf)
|
|
enc.Indent("", " ")
|
|
|
|
if err := xmlutil.BuildXML(payload, enc); err != nil {
|
|
panic(err)
|
|
}
|
|
enc.Flush()
|
|
|
|
return buf, nil
|
|
|
|
}
|
|
|
|
// wrapWithLocation decorates payload with the requested locationName tag.
|
|
func GenerateXMLPayload(locationName string, payload interface{}) interface{} {
|
|
t := reflect.StructOf([]reflect.StructField{
|
|
{
|
|
Name: "Value",
|
|
Type: reflect.TypeOf(payload),
|
|
Tag: reflect.StructTag(`locationName:"` + locationName + `"`),
|
|
},
|
|
})
|
|
|
|
v := reflect.New(t).Elem()
|
|
v.Field(0).Set(reflect.ValueOf(payload))
|
|
return v.Interface()
|
|
}
|