commit e02498ec0ed7ee3550da40593e20e7ed375f48e9 Author: Sean Hickey Date: Wed Nov 9 01:06:01 2022 -0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26f22b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Emacs +*~ + +# Build output +*.test diff --git a/License.md b/License.md new file mode 100644 index 0000000..453e5da --- /dev/null +++ b/License.md @@ -0,0 +1,24 @@ +# BSD 2-Clause License + +Copyright (c) 2022, Sean Hickey (Wisellama) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..35bded0 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +all: linter test + +test: + go test + +lint: linter +linter: + golangci-lint run + +goimports_everything: + find . -name "*.go" -exec goimports -w {} \; diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..565ae90 --- /dev/null +++ b/Readme.md @@ -0,0 +1,13 @@ +# gopackagebase + +```sh +go get git.wisellama.rocks/Wisellama/gopackagebase@v0.0.1 +``` + +This modules contains basic setup and initialization functions that +are useful to run at the beginning of a program. This integrates with +[gosimpleconf][1] to also support parsing config files. + +I wanted to create this package to centralize some of the basic setup +boilerplate I had started to collect in my Go projects. This +repository may be considered an anti-pattern, but it fits my use-case. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c590092 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.wisellama.rocks/gopackagebase + +go 1.17 + +require git.wisellama.rocks/Wisellama/gosimpleconf v0.1.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bd3417a --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +git.wisellama.rocks/Wisellama/gosimpleconf v0.1.0 h1:Z2FAzARct8ShV4NSueC/y+PyuSQVcyo4WnW7GoZ9L10= +git.wisellama.rocks/Wisellama/gosimpleconf v0.1.0/go.mod h1:Gg1vUTBRZD7qcXvdF8L50PsnL9coLt/XbWa5BwSDN/M= diff --git a/gopackagebase.go b/gopackagebase.go new file mode 100644 index 0000000..c05ec3b --- /dev/null +++ b/gopackagebase.go @@ -0,0 +1,53 @@ +package gopackagebase + +import ( + "context" + "log" + + "git.wisellama.rocks/Wisellama/gosimpleconf" +) + +type BaseConfig struct { + Ctx context.Context + Cancel func() + ConfigMap gosimpleconf.ConfigMap +} + +// Initialize will configure all of the base stuff that we want +// commonly set up at the beginning of a Go project. +func Initialize(configFile string, defaultConfig gosimpleconf.ConfigMap) (*BaseConfig, error) { + var err error + + ctx, cancel := InitSignalHandler() + + err = InitRNG() + if err != nil { + return nil, err + } + + configMap, err := gosimpleconf.ConfigureWithDefaults(configFile, defaultConfig) + if err != nil { + log.Fatalf("error during configure: %v\n", err) + } + + writeToFile := gosimpleconf.Bool(configMap["log.writeToFile"]) + logFilename := configMap["log.file"] + + logWriter, err := SetupLogging(writeToFile, logFilename) + if err != nil { + return nil, err + } + + fullCancel := func() { + logWriter.Cleanup() + cancel() + } + + baseConfig := BaseConfig{ + Ctx: ctx, + Cancel: fullCancel, + ConfigMap: configMap, + } + + return &baseConfig, nil +} diff --git a/logging.go b/logging.go new file mode 100644 index 0000000..b44a24d --- /dev/null +++ b/logging.go @@ -0,0 +1,43 @@ +package gopackagebase + +import ( + "fmt" + "log" + "os" + "time" +) + +type logWriter struct { + writeToFile bool + logFile *os.File +} + +func (w *logWriter) Write(bytes []byte) (int, error) { + t := time.Now().UTC().Format(time.RFC3339) + return fmt.Fprintf(w.logFile, "%v %v", t, string(bytes)) +} + +func (w *logWriter) Cleanup() { + defer w.logFile.Close() +} + +func SetupLogging(writeToFile bool, logFilename string) (*logWriter, error) { + var err error + log.SetFlags(0) + + logFile := os.Stdout + if writeToFile { + logFile, err = os.OpenFile(logFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return nil, err + } + } + + writer := &logWriter{ + writeToFile: writeToFile, + logFile: logFile, + } + log.SetOutput(writer) + + return writer, nil +} diff --git a/random.go b/random.go new file mode 100644 index 0000000..c1243b2 --- /dev/null +++ b/random.go @@ -0,0 +1,24 @@ +package gopackagebase + +import ( + crypto_rand "crypto/rand" + "encoding/binary" + "fmt" + math_rand "math/rand" +) + +// InitRNG will grab some cryptographically secure bytes to use as the +// seed for the non-crypto random number generator. Suggested on +// StackOverflow to avoid time-based seeds: +// https://stackoverflow.com/a/54491783 +func InitRNG() error { + var b [8]byte + _, err := crypto_rand.Read(b[:]) + if err != nil { + err = fmt.Errorf("error seeding random number generator: %w", err) + return err + } + math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:]))) + + return nil +} diff --git a/sighandler.go b/sighandler.go new file mode 100644 index 0000000..9c13d74 --- /dev/null +++ b/sighandler.go @@ -0,0 +1,40 @@ +package gopackagebase + +import ( + "context" + "log" + "os" + "os/signal" +) + +func InitSignalHandler() (context.Context, func()) { + // Setup some initial context for gracefully killing the program + // with system interrupt signals like ctrl+c + // https://pace.dev/blog/2020/02/17/repond-to-ctrl-c-interrupt-signals-gracefully-with-context-in-golang-by-mat-ryer.html + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, os.Interrupt) + + // Start a goroutine to listen for signal interrupts + go func() { + select { + case <-signalChan: + // Graceful exit on ctrl+c + log.Printf("Attempting to exit gracefully...\n") + cancel() + case <-ctx.Done(): + } + <-signalChan // Hard exit on second ctrl+c + log.Printf("Hard kill\n") + os.Exit(2) + }() + + // Return a new cancel function that also sends the stop signal. + killFunc := func() { + signal.Stop(signalChan) + cancel() + } + + return ctx, killFunc +}