feat: add cmd flags
This commit is contained in:
+1
-10
@@ -12,7 +12,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
// grab the environment variables from the runtime environment
|
// grab the environment variables from the runtime environment
|
||||||
// unfortunately since its before we configure the loglevel, we cannot use slog logging in those functions
|
// unfortunately since its before we configure the loglevel, we cannot use slog logging in those functions
|
||||||
env := bootstrap.GrabEnvironment()
|
env := bootstrap.LoadConfig()
|
||||||
// configure the logger so we have nice json
|
// configure the logger so we have nice json
|
||||||
level := utility.ParseSlogLevel(env.LogLevel) // defaults to Info
|
level := utility.ParseSlogLevel(env.LogLevel) // defaults to Info
|
||||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||||
@@ -23,15 +23,6 @@ func main() {
|
|||||||
// print our running environment variable set
|
// print our running environment variable set
|
||||||
slog.Debug("displaying environment variables", "environment", env)
|
slog.Debug("displaying environment variables", "environment", env)
|
||||||
|
|
||||||
// TO DO, allow cmd args parsing
|
|
||||||
|
|
||||||
// checking directories to ensure its expected environment is ready
|
|
||||||
slog.Debug("checking if directories are present")
|
|
||||||
if err := bootstrap.EnsureOperation(env.DataDirectory); err != nil {
|
|
||||||
slog.Error("failed to ensure the operating environment", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// initiating the database connection for which we safe things
|
// initiating the database connection for which we safe things
|
||||||
slog.Info("kicking off database connection")
|
slog.Info("kicking off database connection")
|
||||||
db, err := database.KickoffDatabase(env.DataDirectory)
|
db, err := database.KickoffDatabase(env.DataDirectory)
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Environment struct {
|
||||||
|
Version string `env:"VERSION" default:"0.0.1" flag:"version"`
|
||||||
|
Codename string `env:"CODENAME" default:"Magical Anomaly" flag:"codename"`
|
||||||
|
LogLevel string `env:"LOG_LEVEL" default:"debug" flag:"log-level"`
|
||||||
|
|
||||||
|
DataDirectory string `env:"DATA_DIR" default:"./data" flag:"data-dir" usage:"option to specify where the state data gets stored"`
|
||||||
|
ContentDirectory string `env:"CONTENT_DIR" default:"./content" flag:"content-dir" usage:"option to specify where the content gets stored"`
|
||||||
|
Hostname string `env:"HOSTNAME" default:"0.0.0.0" flag:"hostname" usage:"option specify the address/hostname to bind the api server to"`
|
||||||
|
Port int `env:"PORT" default:"8080" flag:"port" usage:"option to specify the port to bind the api server to"`
|
||||||
|
AuthenticationEnabled bool `env:"AUTH_ENABLED" default:"true" flag:"auth" usage:"option to disable authentication"`
|
||||||
|
|
||||||
|
WatchdogEnabled bool `env:"WATCHDOG_ENABLED" default:"true" flag:"watchdog" usage:"option to disable watchdog"`
|
||||||
|
WatchdogInterval int `env:"WATCHDOG_INTERVAL" default:"60" flag:"watchdog-interval" usage:"option to specify the interval in second(s) on which watchdog runs"`
|
||||||
|
WatchdogSyncMode string `env:"WATCHDOG_SYNC_MODE" default:"strict" flag:"watchdog-mode" usage:"option to specify the mode watchdog will run with: strict|sync|dry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFromEnv(env *Environment) {
|
||||||
|
v := reflect.ValueOf(env).Elem()
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
valueField := v.Field(i)
|
||||||
|
|
||||||
|
// It is of great importance we set these fields above
|
||||||
|
key := field.Tag.Get("env")
|
||||||
|
fallback := field.Tag.Get("default")
|
||||||
|
|
||||||
|
// If the variable is not found or is empty
|
||||||
|
raw, ok := os.LookupEnv(key)
|
||||||
|
if !ok || raw == "" {
|
||||||
|
raw = fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
switch valueField.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
valueField.SetString(raw)
|
||||||
|
|
||||||
|
case reflect.Int:
|
||||||
|
if parsed, err := strconv.Atoi(raw); err == nil {
|
||||||
|
valueField.SetInt(int64(parsed))
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
if parsed, err := strconv.ParseBool(raw); err == nil {
|
||||||
|
valueField.SetBool(parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindFlags(env *Environment) {
|
||||||
|
v := reflect.ValueOf(env).Elem()
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
valueField := v.Field(i)
|
||||||
|
|
||||||
|
flagName := field.Tag.Get("flag")
|
||||||
|
flagUsage := field.Tag.Get("usage")
|
||||||
|
if flagName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch valueField.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
flag.StringVar(valueField.Addr().Interface().(*string), flagName,
|
||||||
|
valueField.String(), flagUsage,
|
||||||
|
)
|
||||||
|
case reflect.Int:
|
||||||
|
flag.IntVar(valueField.Addr().Interface().(*int), flagName,
|
||||||
|
int(valueField.Int()), flagUsage,
|
||||||
|
)
|
||||||
|
case reflect.Bool:
|
||||||
|
flag.BoolVar(
|
||||||
|
valueField.Addr().Interface().(*bool), flagName,
|
||||||
|
valueField.Bool(), flagUsage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig() Environment {
|
||||||
|
var env Environment
|
||||||
|
|
||||||
|
loadFromEnv(&env)
|
||||||
|
bindFlags(&env)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Environment struct {
|
|
||||||
// Items made for the presentation of ORBITS
|
|
||||||
Version string
|
|
||||||
Codename string
|
|
||||||
LogLevel string
|
|
||||||
|
|
||||||
// Items made for the configuration of the server parts
|
|
||||||
DataDirectory string
|
|
||||||
ContentDirectory string
|
|
||||||
Hostname string
|
|
||||||
Port int
|
|
||||||
AuthenticationEnabled bool
|
|
||||||
|
|
||||||
// Items made for the server watchdog
|
|
||||||
WatchdogEnabled bool
|
|
||||||
WatchdogInterval int
|
|
||||||
WatchdogSyncMode string
|
|
||||||
}
|
|
||||||
|
|
||||||
func GrabEnvironment() Environment {
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
cwd = "."
|
|
||||||
}
|
|
||||||
|
|
||||||
fbBase := filepath.Join(cwd, "data")
|
|
||||||
fbContent := filepath.Join(fbBase, "content")
|
|
||||||
|
|
||||||
return Environment{
|
|
||||||
// Basic server configuration
|
|
||||||
Version: safeStringGrab("VERSION", "0.0.1"),
|
|
||||||
Codename: safeStringGrab("CODENAME", "Magical Anomaly"),
|
|
||||||
LogLevel: safeStringGrab("LOG_LEVEL", "debug"),
|
|
||||||
|
|
||||||
// GIN API configuration
|
|
||||||
DataDirectory: safeStringGrab("DATA_DIR", fbBase),
|
|
||||||
ContentDirectory: safeStringGrab("CONTENT_DIR", fbContent),
|
|
||||||
Hostname: safeStringGrab("HOSTNAME", "0.0.0.0"),
|
|
||||||
Port: safeIntGrab("PORT", 8080),
|
|
||||||
AuthenticationEnabled: safeBoolGrab("AUTH_ENABLED", true),
|
|
||||||
|
|
||||||
// watchdog configuration
|
|
||||||
// watchdog is the integrity checker/steward
|
|
||||||
WatchdogEnabled: safeBoolGrab("WATCHDOG_ENABLED", true),
|
|
||||||
WatchdogInterval: safeIntGrab("WATCHDOG_INTERVAL", 60),
|
|
||||||
// sync: sync local files to the database, for example when you want to allow local inserting files which then get added to the database
|
|
||||||
// strict: make the database leading, this is when you only allow API uploads to be registered, and remove orphaned filesystem files
|
|
||||||
// dry: do nothing
|
|
||||||
WatchdogSyncMode: safeStringGrab("WATCHDOG_SYNC_MODE", "strict"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// part of filesystem checking
|
|
||||||
func EnsureOperation(workDir string) error {
|
|
||||||
nDirs := []string{
|
|
||||||
workDir,
|
|
||||||
filepath.Join(workDir),
|
|
||||||
filepath.Join(workDir, "content"),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range nDirs {
|
|
||||||
if err := os.MkdirAll(p, 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
validSyncModes = []string{
|
|
||||||
"sync",
|
|
||||||
"strict",
|
|
||||||
"dry",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// part of environment checking
|
|
||||||
func safeStringGrab(key, fallback string) string {
|
|
||||||
if v, ok := os.LookupEnv(key); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
func safeIntGrab(key string, fallback int) int {
|
|
||||||
if v, ok := os.LookupEnv(key); ok {
|
|
||||||
if i, err := strconv.Atoi(v); err == nil {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
func safeBoolGrab(key string, fallback bool) bool {
|
|
||||||
if v, ok := os.LookupEnv(key); ok {
|
|
||||||
if b, err := strconv.ParseBool(v); err == nil {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
func safeSyncModeGrab(key, fallback string) string {
|
|
||||||
if v, ok := os.LookupEnv(key); ok {
|
|
||||||
if slices.Contains(validSyncModes, v) {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
// this function should only be called after manually checking the filetype
|
// this function should only be called after manually checking the filetype
|
||||||
func BuildFileRecord(r io.Reader, origName string, contentDirectory string) (File, error) {
|
func BuildFileRecord(r io.Reader, origName string, contentDirectory string) (File, error) {
|
||||||
ext := filepath.Ext(origName)
|
ext := filepath.Ext(origName)
|
||||||
category, ok := utility.CategorizeMediaType(ext)
|
category := utility.CategorizeMediaType(ext)
|
||||||
if !ok {
|
if category == utility.Unspecified {
|
||||||
return File{}, fmt.Errorf("unsupported filetype")
|
return File{}, fmt.Errorf("unsupported filetype")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,12 +78,14 @@ func watchdog(env bootstrap.Environment, db *gorm.DB) {
|
|||||||
readerStream, err := os.Open(fp)
|
readerStream, err := os.Open(fp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to a reader stream")
|
slog.Error("failed to a reader stream")
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
defer readerStream.Close()
|
defer readerStream.Close()
|
||||||
|
|
||||||
fileData, err := BuildFileRecord(readerStream, filepath.Base(fp), env.ContentDirectory)
|
fileData, err := BuildFileRecord(readerStream, filepath.Base(fp), env.ContentDirectory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to enroll local file into the database", "error", err)
|
slog.Error("failed to enroll local file into the database", "error", err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := RegisterFile(db, fileData); err != nil {
|
if err := RegisterFile(db, fileData); err != nil {
|
||||||
@@ -92,6 +94,7 @@ func watchdog(env bootstrap.Environment, db *gorm.DB) {
|
|||||||
} else {
|
} else {
|
||||||
slog.Error("failed to insert filedata to the database", "error", err)
|
slog.Error("failed to insert filedata to the database", "error", err)
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// to fully finalize the enrollment process, we rename the locally inserted file to a unique filename
|
// to fully finalize the enrollment process, we rename the locally inserted file to a unique filename
|
||||||
@@ -99,7 +102,6 @@ func watchdog(env bootstrap.Environment, db *gorm.DB) {
|
|||||||
if err := os.Rename(fp, fileData.FilePath); err != nil {
|
if err := os.Rename(fp, fileData.FilePath); err != nil {
|
||||||
slog.Error("failed to move the locally inserted file", "error", err)
|
slog.Error("failed to move the locally inserted file", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "strict":
|
case "strict":
|
||||||
err := utility.RemoveFile(fp)
|
err := utility.RemoveFile(fp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -107,6 +109,8 @@ func watchdog(env bootstrap.Environment, db *gorm.DB) {
|
|||||||
}
|
}
|
||||||
case "dry":
|
case "dry":
|
||||||
slog.Debug("dry mode enabled, not purging", "filepath", fp)
|
slog.Debug("dry mode enabled, not purging", "filepath", fp)
|
||||||
|
default:
|
||||||
|
slog.Warn("unknown watchdog mode", "mode", env.WatchdogSyncMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package utility
|
|||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,20 +16,18 @@ func RemoveFile(p string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CategorizeMediaType(ext string) (MediaType, bool) {
|
func CategorizeMediaType(ext string) MediaType {
|
||||||
// Lets categorize
|
// Lets categorize
|
||||||
|
|
||||||
/*
|
if slices.Contains(videoFormats, ext) {
|
||||||
switch ext {
|
return Video
|
||||||
case slices.Contains(videoFormats, ext):
|
}
|
||||||
return Video, true
|
|
||||||
case slices.Contains(presentationFormats, ext):
|
if slices.Contains(presentationFormats, ext) {
|
||||||
return Presentation, true
|
return Presentation
|
||||||
default:
|
}
|
||||||
slog.Debug("marking file as invalid due to its undefined extension")
|
|
||||||
return "", false
|
return Unspecified
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseSlogLevel(s string) slog.Level {
|
func ParseSlogLevel(s string) slog.Level {
|
||||||
|
|||||||
Reference in New Issue
Block a user