[WIP] Initial commit
This commit is contained in:
10
internal/doll/globals.go
Normal file
10
internal/doll/globals.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package doll
|
||||
|
||||
// Instance global variable that contains all variables defined in jsons describing hosts, host groups and services.
|
||||
var Instance map[string]any
|
||||
|
||||
// Facts global variable that contains all data that host provides us with.
|
||||
// TODO: think about struct format for this data. As log as it is machine-written encoding/json must be used.
|
||||
var Facts map[string]any
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
30
internal/doll/jsonfile.go
Normal file
30
internal/doll/jsonfile.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package doll
|
||||
|
||||
import (
|
||||
"doll/internal/log"
|
||||
"encoding/json"
|
||||
"os"
|
||||
)
|
||||
|
||||
// LoadJsonFile loads data from json to Instance global variable map.
|
||||
func LoadJsonFile(file string) map[string]any {
|
||||
var (
|
||||
err error
|
||||
data []byte
|
||||
)
|
||||
|
||||
data, err = os.ReadFile(file)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to load %s: %s", file, err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(data), &Instance)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to marshal JSON data from %s: %s", file, err)
|
||||
}
|
||||
|
||||
return Instance
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
3
internal/doll/types.go
Normal file
3
internal/doll/types.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package doll
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
176
internal/log/log.go
Normal file
176
internal/log/log.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Handler exposes slog handler struct.
|
||||
var Handler slog.Handler
|
||||
|
||||
// Ctx log context. For now it is just sits here and do nothing.
|
||||
var Ctx = context.Background()
|
||||
|
||||
// Writer go-writer eported for capturing other types of logs, even for std log go facility.
|
||||
var Writer io.Writer
|
||||
|
||||
// reader is required for Writer to work properly. It is connected via io.Pipe() to Writer.
|
||||
var reader io.Reader
|
||||
|
||||
// Init setup logger stuff.
|
||||
// level can be error, warn, info, debug if something other supplied info level selected.
|
||||
// fileDescriptor should be opened before supplying it to Init().
|
||||
func Init(level string, fileDescriptor *os.File) {
|
||||
var loglevel slog.Level
|
||||
|
||||
// no panic, no trace.
|
||||
switch level {
|
||||
case "debug":
|
||||
loglevel = slog.LevelDebug
|
||||
|
||||
case "info":
|
||||
loglevel = slog.LevelInfo
|
||||
|
||||
case "warn":
|
||||
loglevel = slog.LevelWarn
|
||||
|
||||
case "error":
|
||||
loglevel = slog.LevelError
|
||||
|
||||
default:
|
||||
loglevel = slog.LevelInfo
|
||||
}
|
||||
|
||||
opts := &slog.HandlerOptions{ //nolint: exhaustruct
|
||||
// Use the ReplaceAttr function on the handler options
|
||||
// to be able to replace any single attribute in the log output
|
||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { //nolint: revive
|
||||
// check that we are handling the time key
|
||||
if a.Key != slog.TimeKey {
|
||||
return a
|
||||
}
|
||||
|
||||
t := a.Value.Time()
|
||||
|
||||
// change the value from a time.Time to a String
|
||||
// where the string has the correct time format.
|
||||
a.Value = slog.StringValue(t.Format(time.DateTime))
|
||||
|
||||
return a
|
||||
},
|
||||
|
||||
Level: loglevel,
|
||||
}
|
||||
|
||||
Handler = slog.NewTextHandler(fileDescriptor, opts)
|
||||
|
||||
slog.SetDefault(
|
||||
slog.New(Handler),
|
||||
)
|
||||
|
||||
reader, Writer = io.Pipe()
|
||||
|
||||
go readIoPipe()
|
||||
}
|
||||
|
||||
// readIoPipe reads pipe and writes it to slog.
|
||||
func readIoPipe() {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
for {
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text() // Get the current line as a string
|
||||
slog.Debug(line)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
// io.EOF is expected when done reading, e.g. on exit.
|
||||
if errors.Is(err, io.EOF) {
|
||||
return
|
||||
}
|
||||
|
||||
// something weird happen, we should it log to guess later what it was.
|
||||
Errorf("Error during scanning log writer pipe: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetLevel returns current loglevel.
|
||||
func GetLevel() string {
|
||||
switch {
|
||||
case Handler.Enabled(Ctx, slog.LevelDebug):
|
||||
return "debug"
|
||||
|
||||
case Handler.Enabled(Ctx, slog.LevelInfo):
|
||||
return "info"
|
||||
|
||||
case Handler.Enabled(Ctx, slog.LevelWarn):
|
||||
return "warn"
|
||||
|
||||
case Handler.Enabled(Ctx, slog.LevelError):
|
||||
return "error"
|
||||
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Fatal passes message directly to slog.Error() and exits with error code 1.
|
||||
func Fatal(message string) {
|
||||
slog.Error(message)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalf logs message on error log level and exits with error code 1. It allows to supply arguments in printf() style .
|
||||
func Fatalf(format string, a ...any) {
|
||||
slog.Error(fmt.Sprintf(format, a...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Error passes message directly to slog.Error().
|
||||
func Error(message string) {
|
||||
slog.Error(message)
|
||||
}
|
||||
|
||||
// Errorf logs message on error log level and allows to supply arguments in printf() style.
|
||||
func Errorf(format string, a ...any) {
|
||||
slog.Error(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Warn passes message directly to slog.Warn().
|
||||
func Warn(message string) {
|
||||
slog.Warn(message)
|
||||
}
|
||||
|
||||
// Warnf logs message on warn log level and allows to supply arguments in printf() style.
|
||||
func Warnf(format string, a ...any) {
|
||||
slog.Warn(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Info passes message directly to slog.Info().
|
||||
func Info(message string) {
|
||||
slog.Info(message)
|
||||
}
|
||||
|
||||
// Infof logs message on info log level and allows to supply arguments in printf() style.
|
||||
func Infof(format string, a ...any) {
|
||||
slog.Info(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Debug passes message directly to slog.Debug().
|
||||
func Debug(message string) {
|
||||
slog.Debug(message)
|
||||
}
|
||||
|
||||
// Debugf logs message on debug log level and allows to supply arguments in printf() style.
|
||||
func Debugf(format string, a ...any) {
|
||||
slog.Debug(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
100
internal/serverConfig/readConfig.go
Normal file
100
internal/serverConfig/readConfig.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package serverSonfig
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"doll/internal/log"
|
||||
|
||||
hjson "github.com/hjson/hjson-go/v4"
|
||||
)
|
||||
|
||||
// MyConfig config structure prototype.
|
||||
type MyConfig struct {
|
||||
Loglevel string `json:"loglevel,omitempty"`
|
||||
Logfile string `json:"logfile,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
// Config contains application running config data.
|
||||
Config MyConfig
|
||||
)
|
||||
|
||||
// ReadConfig reads and validates config file. Setups defaults where possible if values are not provided.
|
||||
func ReadConfig(location string) error { //nolint:gocognit,gocyclo
|
||||
var (
|
||||
err error
|
||||
configLoaded bool
|
||||
)
|
||||
|
||||
fileInfo, err := os.Stat(location)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read %s: %w", location, err)
|
||||
}
|
||||
|
||||
// Config file seems to be too long for valid config.
|
||||
if fileInfo.Size() > 65535 {
|
||||
return fmt.Errorf("config file %s is too long for config, skipping", location)
|
||||
}
|
||||
|
||||
buf, err := os.ReadFile(location)
|
||||
|
||||
// Config file is unreadable for some reasons.
|
||||
if err != nil {
|
||||
return fmt.Errorf("Skip reading config file %s: %s", location, err)
|
||||
}
|
||||
|
||||
// According to docs hjson claims to be able to pase bad json, but parses it to go map.
|
||||
// It is more interesting to get strict as result, so we parse data to go map, then make json out of this map and
|
||||
// then parse this json to struct. It is not the most effective and speedy solution but it works and this action
|
||||
// been performed not so often.
|
||||
var (
|
||||
sampleConfig MyConfig
|
||||
tmp map[string]interface{}
|
||||
)
|
||||
|
||||
err = hjson.Unmarshal(buf, &tmp)
|
||||
|
||||
// Unable to parse json to map, that is unfortunate.
|
||||
if err != nil {
|
||||
return fmt.Errorf("Skip parsing config file %s: %s", location, err)
|
||||
}
|
||||
|
||||
tmpJSON, err := json.Marshal(tmp)
|
||||
|
||||
// Unable to parse map to json, well, we can nothing to with that.
|
||||
if err != nil {
|
||||
return fmt.Errorf("Skip parsing config file %s: %s", location, err)
|
||||
}
|
||||
|
||||
// Unable to parse json to struct, just bail it here.
|
||||
if err := json.Unmarshal(tmpJSON, &sampleConfig); err != nil {
|
||||
return fmt.Errorf("Skip parsing config file %s: %s", location, err)
|
||||
}
|
||||
|
||||
// Time to validate config values here.
|
||||
if sampleConfig.Loglevel == "" {
|
||||
sampleConfig.Loglevel = "info"
|
||||
|
||||
log.Info("loglevel not defined in config, using info")
|
||||
}
|
||||
|
||||
// sampleConfig.Log = "" if not set
|
||||
|
||||
// TODO: validate the rest values fron config.
|
||||
|
||||
Config = sampleConfig
|
||||
configLoaded = true
|
||||
|
||||
log.Infof("Using %s as config file", location)
|
||||
|
||||
if !configLoaded {
|
||||
return fmt.Errorf("config was not loaded!") //nolint:goerr113,revive,stylecheck
|
||||
}
|
||||
|
||||
return err //nolint:wrapcheck
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
Reference in New Issue
Block a user