2023-09-03 12:13:18 +02:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
|
|
|
_ "embed"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"regexp"
|
2023-10-21 12:00:00 +02:00
|
|
|
"strings"
|
2023-10-18 12:00:00 +02:00
|
|
|
|
|
|
|
"framagit.org/ppom/reaction/logger"
|
2023-09-03 12:13:18 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func addStringFlag(names []string, defvalue string, f *flag.FlagSet) *string {
|
|
|
|
var value string
|
|
|
|
for _, name := range names {
|
|
|
|
f.StringVar(&value, name, defvalue, "")
|
|
|
|
}
|
|
|
|
return &value
|
|
|
|
}
|
|
|
|
|
|
|
|
func addBoolFlag(names []string, f *flag.FlagSet) *bool {
|
|
|
|
var value bool
|
|
|
|
for _, name := range names {
|
|
|
|
f.BoolVar(&value, name, false, "")
|
|
|
|
}
|
|
|
|
return &value
|
|
|
|
}
|
|
|
|
|
|
|
|
var SocketPath *string
|
|
|
|
|
|
|
|
func addSocketFlag(f *flag.FlagSet) *string {
|
2023-09-05 16:44:40 +02:00
|
|
|
return addStringFlag([]string{"s", "socket"}, "/run/reaction/reaction.sock", f)
|
2023-09-03 12:13:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func addConfFlag(f *flag.FlagSet) *string {
|
2023-09-05 16:44:40 +02:00
|
|
|
return addStringFlag([]string{"c", "config"}, "", f)
|
2023-09-03 12:13:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func addFormatFlag(f *flag.FlagSet) *string {
|
2023-09-05 16:44:40 +02:00
|
|
|
return addStringFlag([]string{"f", "format"}, "yaml", f)
|
|
|
|
}
|
|
|
|
|
|
|
|
func addLimitFlag(f *flag.FlagSet) *string {
|
|
|
|
return addStringFlag([]string{"l", "limit"}, "", f)
|
2023-09-03 12:13:18 +02:00
|
|
|
}
|
|
|
|
|
2023-10-18 12:00:00 +02:00
|
|
|
func addLevelFlag(f *flag.FlagSet) *string {
|
|
|
|
return addStringFlag([]string{"l", "loglevel"}, "INFO", f)
|
|
|
|
}
|
|
|
|
|
2023-10-22 12:00:00 +02:00
|
|
|
func addPatternFlag(f *flag.FlagSet) *string {
|
|
|
|
return addStringFlag([]string{"p", "pattern"}, "", f)
|
2023-10-21 12:00:00 +02:00
|
|
|
}
|
|
|
|
|
2023-09-03 12:13:18 +02:00
|
|
|
func subCommandParse(f *flag.FlagSet, maxRemainingArgs int) {
|
|
|
|
help := addBoolFlag([]string{"h", "help"}, f)
|
|
|
|
f.Parse(os.Args[2:])
|
|
|
|
if *help {
|
|
|
|
basicUsage()
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
if len(f.Args()) > maxRemainingArgs {
|
|
|
|
fmt.Printf("ERROR unrecognized argument(s): %v\n", f.Args()[maxRemainingArgs:])
|
|
|
|
basicUsage()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func basicUsage() {
|
|
|
|
const (
|
|
|
|
bold = "\033[1m"
|
|
|
|
reset = "\033[0m"
|
|
|
|
)
|
2023-12-31 12:00:00 +01:00
|
|
|
fmt.Print(
|
|
|
|
bold + `reaction help` + reset + `
|
|
|
|
# print this help message
|
|
|
|
|
|
|
|
` + bold + `reaction start` + reset + `
|
2023-09-03 12:13:18 +02:00
|
|
|
# start the daemon
|
|
|
|
|
|
|
|
# options:
|
2023-10-22 12:00:00 +02:00
|
|
|
-c/--config CONFIG_FILE # configuration file in json, jsonnet or yaml format (required)
|
|
|
|
-l/--loglevel LEVEL # minimum log level to show, in DEBUG, INFO, WARN, ERROR, FATAL
|
|
|
|
# (default: INFO)
|
|
|
|
-s/--socket SOCKET # path to the client-daemon communication socket
|
|
|
|
# (default: /run/reaction/reaction.sock)
|
2023-09-03 12:13:18 +02:00
|
|
|
|
|
|
|
` + bold + `reaction example-conf` + reset + `
|
|
|
|
# print a configuration file example
|
|
|
|
|
2023-09-05 16:44:40 +02:00
|
|
|
` + bold + `reaction show` + reset + `
|
2023-10-04 12:00:00 +02:00
|
|
|
# show current matches and which actions are still to be run
|
2023-09-03 12:13:18 +02:00
|
|
|
# (e.g know what is currenly banned)
|
|
|
|
|
|
|
|
# options:
|
2023-10-22 12:00:00 +02:00
|
|
|
-s/--socket SOCKET # path to the client-daemon communication socket
|
|
|
|
-f/--format yaml|json # (default: yaml)
|
|
|
|
-l/--limit STREAM[.FILTER] # only show items related to this STREAM (or STREAM.FILTER)
|
|
|
|
-p/--pattern PATTERN # only show items matching the PATTERN regex
|
2023-09-03 12:13:18 +02:00
|
|
|
|
2023-09-05 16:44:40 +02:00
|
|
|
` + bold + `reaction flush` + reset + ` TARGET
|
2023-10-04 12:00:00 +02:00
|
|
|
# run currently active matches and pending actions for the specified TARGET
|
|
|
|
# (then show flushed matches and actions)
|
2023-09-03 12:13:18 +02:00
|
|
|
|
|
|
|
# options:
|
2023-10-22 12:00:00 +02:00
|
|
|
-s/--socket SOCKET # path to the client-daemon communication socket
|
|
|
|
-f/--format yaml|json # (default: yaml)
|
2023-09-03 12:13:18 +02:00
|
|
|
|
2023-10-04 12:00:00 +02:00
|
|
|
` + bold + `reaction test-regex` + reset + ` REGEX LINE # test REGEX against LINE
|
|
|
|
cat FILE | ` + bold + `reaction test-regex` + reset + ` REGEX # test REGEX against each line of FILE
|
2023-12-04 12:00:00 +01:00
|
|
|
|
2023-12-31 12:00:00 +01:00
|
|
|
` + bold + `reaction version` + reset + `
|
|
|
|
# print version information
|
|
|
|
|
2023-12-04 12:00:00 +01:00
|
|
|
see usage examples, service configurations and good practices
|
|
|
|
on the ` + bold + `wiki` + reset + `: https://framagit.org/ppom/reaction-wiki
|
2023-09-03 12:13:18 +02:00
|
|
|
`)
|
|
|
|
}
|
|
|
|
|
2023-10-12 12:00:00 +02:00
|
|
|
//go:embed example.yml
|
2023-09-03 12:13:18 +02:00
|
|
|
var exampleConf string
|
|
|
|
|
2023-12-31 12:00:00 +01:00
|
|
|
func Main(version, commit string) {
|
2023-09-03 12:13:18 +02:00
|
|
|
if len(os.Args) <= 1 {
|
2023-10-22 12:00:00 +02:00
|
|
|
logger.Fatalln("No argument provided. Try `reaction help`")
|
2023-09-03 12:13:18 +02:00
|
|
|
basicUsage()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2023-10-21 12:00:00 +02:00
|
|
|
f := flag.NewFlagSet(os.Args[1], flag.ExitOnError)
|
2023-09-03 12:13:18 +02:00
|
|
|
switch os.Args[1] {
|
2023-10-22 12:00:00 +02:00
|
|
|
case "help", "-h", "-help", "--help":
|
2023-09-03 12:13:18 +02:00
|
|
|
basicUsage()
|
|
|
|
|
2023-12-31 12:00:00 +01:00
|
|
|
case "version", "-v", "--version":
|
|
|
|
fmt.Printf("reaction version %v commit %v\n", commit, version)
|
|
|
|
|
2023-09-03 12:13:18 +02:00
|
|
|
case "example-conf":
|
|
|
|
subCommandParse(f, 0)
|
|
|
|
fmt.Print(exampleConf)
|
|
|
|
|
|
|
|
case "start":
|
|
|
|
SocketPath = addSocketFlag(f)
|
|
|
|
confFilename := addConfFlag(f)
|
2023-10-18 12:00:00 +02:00
|
|
|
logLevel := addLevelFlag(f)
|
2023-09-03 12:13:18 +02:00
|
|
|
subCommandParse(f, 0)
|
|
|
|
if *confFilename == "" {
|
2023-10-18 12:00:00 +02:00
|
|
|
logger.Fatalln("no configuration file provided")
|
|
|
|
basicUsage()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
logLevelType := logger.FromString(*logLevel)
|
|
|
|
if logLevelType == logger.UNKNOWN {
|
|
|
|
logger.Fatalf("Log Level %v not recognized", logLevel)
|
2023-09-03 12:13:18 +02:00
|
|
|
basicUsage()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2023-10-18 12:00:00 +02:00
|
|
|
logger.SetLogLevel(logLevelType)
|
2023-09-03 12:13:18 +02:00
|
|
|
Daemon(*confFilename)
|
|
|
|
|
|
|
|
case "show":
|
|
|
|
SocketPath = addSocketFlag(f)
|
|
|
|
queryFormat := addFormatFlag(f)
|
2023-09-05 16:44:40 +02:00
|
|
|
limit := addLimitFlag(f)
|
2023-10-22 12:00:00 +02:00
|
|
|
pattern := addPatternFlag(f)
|
2023-09-05 16:44:40 +02:00
|
|
|
subCommandParse(f, 0)
|
2023-10-04 12:00:00 +02:00
|
|
|
if *queryFormat != "yaml" && *queryFormat != "json" {
|
2023-10-18 12:00:00 +02:00
|
|
|
logger.Fatalln("only yaml and json formats are supported")
|
2023-10-04 12:00:00 +02:00
|
|
|
f.PrintDefaults()
|
2023-09-03 12:13:18 +02:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
2023-10-21 12:00:00 +02:00
|
|
|
stream, filter := "", ""
|
2023-09-05 16:44:40 +02:00
|
|
|
if *limit != "" {
|
2023-10-21 12:00:00 +02:00
|
|
|
splitSF := strings.Split(*limit, ".")
|
|
|
|
stream = splitSF[0]
|
|
|
|
if len(splitSF) == 2 {
|
|
|
|
filter = splitSF[1]
|
|
|
|
} else if len(splitSF) > 2 {
|
|
|
|
logger.Fatalln("-l/--limit: only one . separator is supported")
|
|
|
|
}
|
2023-09-03 12:13:18 +02:00
|
|
|
}
|
2023-10-22 12:00:00 +02:00
|
|
|
var regex *regexp.Regexp
|
|
|
|
var err error
|
|
|
|
if *pattern != "" {
|
|
|
|
regex, err = regexp.Compile(*pattern)
|
|
|
|
if err != nil {
|
|
|
|
logger.Fatalln("-p/--pattern: ", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ClientShow(*queryFormat, stream, filter, regex)
|
2023-09-03 12:13:18 +02:00
|
|
|
|
|
|
|
case "flush":
|
|
|
|
SocketPath = addSocketFlag(f)
|
2023-10-04 12:00:00 +02:00
|
|
|
queryFormat := addFormatFlag(f)
|
2023-09-05 16:44:40 +02:00
|
|
|
limit := addLimitFlag(f)
|
|
|
|
subCommandParse(f, 1)
|
2023-10-04 12:00:00 +02:00
|
|
|
if *queryFormat != "yaml" && *queryFormat != "json" {
|
2023-10-18 12:00:00 +02:00
|
|
|
logger.Fatalln("only yaml and json formats are supported")
|
2023-10-04 12:00:00 +02:00
|
|
|
f.PrintDefaults()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2023-09-03 12:13:18 +02:00
|
|
|
if f.Arg(0) == "" {
|
2023-10-18 12:00:00 +02:00
|
|
|
logger.Fatalln("subcommand flush takes one TARGET argument")
|
2023-09-05 16:44:40 +02:00
|
|
|
basicUsage()
|
2023-09-03 12:13:18 +02:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
2023-09-05 16:44:40 +02:00
|
|
|
if *limit != "" {
|
2023-10-18 12:00:00 +02:00
|
|
|
logger.Fatalln("for now, -l/--limit is not supported")
|
2023-09-03 12:13:18 +02:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
2023-10-04 12:00:00 +02:00
|
|
|
ClientFlush(f.Arg(0), *limit, *queryFormat)
|
2023-09-03 12:13:18 +02:00
|
|
|
|
|
|
|
case "test-regex":
|
|
|
|
// socket not needed, no interaction with the daemon
|
|
|
|
subCommandParse(f, 2)
|
|
|
|
if f.Arg(0) == "" {
|
2023-10-18 12:00:00 +02:00
|
|
|
logger.Fatalln("subcommand test-regex takes at least one REGEX argument")
|
2023-09-03 12:13:18 +02:00
|
|
|
basicUsage()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
regex, err := regexp.Compile(f.Arg(0))
|
|
|
|
if err != nil {
|
2023-10-18 12:00:00 +02:00
|
|
|
logger.Fatalln("ERROR the specified regex is invalid: %v", err)
|
2023-09-03 12:13:18 +02:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
if f.Arg(1) == "" {
|
2023-10-18 12:00:00 +02:00
|
|
|
logger.Println(logger.INFO, "no second argument: reading from stdin")
|
2023-09-03 12:13:18 +02:00
|
|
|
|
|
|
|
MatchStdin(regex)
|
|
|
|
} else {
|
|
|
|
Match(regex, f.Arg(1))
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
2023-10-22 12:00:00 +02:00
|
|
|
logger.Fatalf("subcommand %v not recognized. Try `reaction help`", os.Args[1])
|
2023-09-03 12:13:18 +02:00
|
|
|
basicUsage()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|