Compare commits
9 Commits
Author | SHA1 | Date |
---|---|---|
Alexander Andreev | 77af8817fc | |
Alexander Andreev | 37bc8b0f1b | |
Alexander Andreev | 64b5966b48 | |
Alexander Andreev | 99d53b31a0 | |
Alexander Andreev | dd2614102e | |
Alexander Andreev | 0cafa69cab | |
Alexander Andreev | ffb401fd9b | |
Alexander Andreev | df3376bc69 | |
Alexander Andreev | 37105a9c8a |
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2021-2023 Alexander "Arav" Andreev <me@arav.su>
|
||||
Copyright (c) 2021-2024 Alexander "Arav" Andreev <me@arav.su>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
|
2
Makefile
2
Makefile
|
@ -6,7 +6,7 @@ SYSDDIR=${SYSDDIR_:/%=%}
|
|||
DESTDIR:=
|
||||
PREFIX:=/usr/local
|
||||
|
||||
VERSION:=3.1.0
|
||||
VERSION:=3.1.1
|
||||
|
||||
FLAGS:=-buildmode=pie -modcacherw -mod=readonly -trimpath
|
||||
LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags netgo
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
|
||||
pkgname=httpprocprobed
|
||||
pkgver=3.1.0
|
||||
pkgver=3.1.1
|
||||
pkgrel=1
|
||||
pkgdesc="HTTPProcProbeD hands out an HTTP endpoint to get if processes are running."
|
||||
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
|
|
@ -1,38 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
ListenAddress string `json:"listen-address"`
|
||||
Processes []Process `json:"processes"`
|
||||
}
|
||||
|
||||
func LoadConfiguration(path string) (conf *Configuration, err error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
conf = &Configuration{}
|
||||
|
||||
if err := json.NewDecoder(f).Decode(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(conf.Processes); i++ {
|
||||
if conf.Processes[i].Process == "" {
|
||||
return nil, fmt.Errorf("an empty process field found")
|
||||
}
|
||||
|
||||
if conf.Processes[i].Alias == "" {
|
||||
conf.Processes[i].Alias = conf.Processes[i].Process
|
||||
}
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
103
main.go
103
main.go
|
@ -18,12 +18,66 @@ var showVersion *bool = flag.Bool("v", false, "show version")
|
|||
|
||||
var version string
|
||||
|
||||
// Process contains an alias that will be returned when queried, and a process
|
||||
// name to look for.
|
||||
// A process is effectively a substring that is being looked in a cmdline file
|
||||
// in /proc/ dir on unix-like systems.
|
||||
type Process struct {
|
||||
Alias string `json:"alias"`
|
||||
Process string `json:"process"`
|
||||
}
|
||||
|
||||
// ProcessesState is a map of processes' aliases and its statuses.
|
||||
type ProcessesState map[string]bool
|
||||
|
||||
func GetProcessesState(procs *[]Process) (ps ProcessesState) {
|
||||
ps = make(ProcessesState)
|
||||
|
||||
for _, proc := range *procs {
|
||||
pids, err := GetProcessPIDs(proc.Process)
|
||||
ps[proc.Alias] = err == nil && len(pids) > 0
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Configuration struct {
|
||||
ListenAddress string `json:"listen-address"`
|
||||
Processes []Process `json:"processes"`
|
||||
}
|
||||
|
||||
func LoadConfiguration(path string) (conf *Configuration, err error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
conf = &Configuration{}
|
||||
|
||||
if err := json.NewDecoder(f).Decode(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(conf.Processes); i++ {
|
||||
if conf.Processes[i].Process == "" {
|
||||
return nil, fmt.Errorf("an empty process field found")
|
||||
}
|
||||
|
||||
if conf.Processes[i].Alias == "" {
|
||||
conf.Processes[i].Alias = conf.Processes[i].Process
|
||||
}
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
flag.Parse()
|
||||
|
||||
if *showVersion {
|
||||
fmt.Println("httpprocprobed ver.", version, "Copyright (c) 2021-2023 Alexander \"Arav\" Andreev <me@arav.su>")
|
||||
fmt.Fprintln(os.Stderr, "httpprocprobed ver.", version, "\nCopyright (c) 2021-2024 Alexander \"Arav\" Andreev <me@arav.su>")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
|
@ -33,7 +87,19 @@ func main() {
|
|||
}
|
||||
|
||||
router := http.NewServeMux()
|
||||
router.HandleFunc("/processes", AreProcessesUp(&conf.Processes))
|
||||
router.HandleFunc("/processes", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
w.Header().Add("Allow", "GET")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(GetProcessesState(&conf.Processes)); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
log.Printf("Failed to encode a process list: %s\n", err)
|
||||
}
|
||||
})
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: conf.ListenAddress,
|
||||
|
@ -48,47 +114,28 @@ func main() {
|
|||
log.Fatalf("ListenAndServe: %s\n", err)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
srv.SetKeepAlivesEnabled(false)
|
||||
if err := srv.Shutdown(context.Background()); err != nil {
|
||||
log.Fatalf("%s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
syssignal := make(chan os.Signal, 1)
|
||||
signal.Notify(syssignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
|
||||
log.Printf("httpprocprobed is running on \"%s\".", conf.ListenAddress)
|
||||
|
||||
for {
|
||||
switch <-syssignal {
|
||||
case os.Interrupt:
|
||||
fallthrough
|
||||
case syscall.SIGINT | syscall.SIGTERM:
|
||||
srv.SetKeepAlivesEnabled(false)
|
||||
if err := srv.Shutdown(context.Background()); err != nil {
|
||||
log.Fatalf("%s\n", err)
|
||||
}
|
||||
log.Println("Server shutted down.")
|
||||
os.Exit(0)
|
||||
return
|
||||
case syscall.SIGHUP:
|
||||
newconf, err := LoadConfiguration(*configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to reload a list of processes from configuration: %s\n", err)
|
||||
}
|
||||
conf.Processes = newconf.Processes
|
||||
log.Println("Successfully reloaded a list of watched processes.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AreProcessesUp sends back status of watched processes.
|
||||
func AreProcessesUp(processes *[]Process) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
w.Header().Add("Allow", "GET")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(GetProcessesState(processes)); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
log.Printf("Failed to encode a process list: %s\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
22
process.go
22
process.go
|
@ -1,22 +0,0 @@
|
|||
package main
|
||||
|
||||
// Process contains an alias that will be returned when queries, and a process
|
||||
// name to look for.
|
||||
type Process struct {
|
||||
Alias string `json:"alias"`
|
||||
Process string `json:"process"`
|
||||
}
|
||||
|
||||
// ProcessesState is a map of processes' aliases and its statuses.
|
||||
type ProcessesState map[string]bool
|
||||
|
||||
func GetProcessesState(procs *[]Process) (ps ProcessesState) {
|
||||
ps = make(ProcessesState)
|
||||
|
||||
for _, proc := range *procs {
|
||||
pids, err := GetProcessPIDs(proc.Process)
|
||||
ps[proc.Alias] = err == nil && len(pids) > 0
|
||||
}
|
||||
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue