From e02498ec0ed7ee3550da40593e20e7ed375f48e9 Mon Sep 17 00:00:00 2001 From: Sean Hickey Date: Wed, 9 Nov 2022 01:06:01 -0800 Subject: [PATCH] Initial commit --- .gitignore | 5 +++++ License.md | 24 ++++++++++++++++++++++ Makefile | 11 ++++++++++ Readme.md | 13 ++++++++++++ go.mod | 5 +++++ go.sum | 2 ++ gopackagebase.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ logging.go | 43 +++++++++++++++++++++++++++++++++++++++ random.go | 24 ++++++++++++++++++++++ sighandler.go | 40 ++++++++++++++++++++++++++++++++++++ 10 files changed, 220 insertions(+) create mode 100644 .gitignore create mode 100644 License.md create mode 100644 Makefile create mode 100644 Readme.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 gopackagebase.go create mode 100644 logging.go create mode 100644 random.go create mode 100644 sighandler.go 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 +}