commit this WIP stuff to a feature branch

This commit is contained in:
brent saner
2025-12-06 19:19:53 -05:00
parent 803be548cf
commit 241a46c9b4
8 changed files with 981 additions and 317 deletions

View File

@@ -1,17 +1,88 @@
package envs
import (
"math"
"os"
"reflect"
"strings"
"sync"
"r00t2.io/goutils/multierr"
"r00t2.io/goutils/structutils"
"github.com/shirou/gopsutil/v4/process"
"r00t2.io/sysutils/errs"
"r00t2.io/sysutils/internal"
)
/*
Current returns a *copy* of the StaticEnv for this current process' environment.
It is set to dynamically refresh with strictRefresh mode set to false.
(see [NewEnvFromPid] docs for what these do/mean.)
Assuming permissions haven't wildly gone silly during runtime, it shouldn't ever
have issues with dynamic refreshing.
It will never panic regardless.
*/
func Current() (s *StaticEnv) {
s = &StaticEnv{
dynamic: defEnv.dynamic,
envVars: defEnv.GetEnvMap(),
}
for k, v := range defEnv.envVars {
s.envVars[k] = v
}
return
}
/*
NewEnvFromMap returns a [StaticEnv] from a fixed list of environment variables.
This is primarily useful for mocking and other tests.
*/
func NewEnvFromMap(envMap map[string]string) (s *StaticEnv, err error) {
if envMap == nil {
err = errs.ErrNilPtr
}
s = &StaticEnv{
envVars: envMap,
}
return
}
/*
NewEnvFromPid returns a [StaticEnv] from a given PID.
If dynamicRefresh is true, the env vars will be refreshed from the process
on every method call.
Note that this will obviously cause errors/panics if the process it binds to disappears
during runtime,
or
*/
func NewEnvFromPid(pid uint, dynamicRefresh, strictRefresh bool) (s *StaticEnv, err error) {
if pid > math.MaxInt32 {
err = errs.ErrHighPid
return
}
s = &StaticEnv{
dynamic: dynamicRefresh,
}
if s.proc, err = process.NewProcess(int32(pid)); err != nil {
return
}
if err = s.Refresh(); err != nil {
return
}
// Test the ability to attach to procs.
err = s.platChecks()
s.strict = strictRefresh
return
}
/*
DefEnv operates like Python's .get() method on dicts (maps);
if the environment variable specified by key does not exist/is not specified,
@@ -98,6 +169,18 @@ func GetEnvMap() (envVars map[string]string) {
return
}
// GetEnvMust wraps GetEnvErr but will panic on error.
func GetEnvMust(key string) (value string) {
var err error
if value, err = GetEnvErr(key); err != nil {
panic(err)
}
return
}
/*
GetEnvMapNative returns a map of all environment variables, but attempts to "nativize" them.
All values are interfaces. It is up to the caller to typeswitch them to proper types.
@@ -331,312 +414,3 @@ func InterpolateString(s *string) (err error) {
return
}
// interpolateMap is used by [Interpolate] for maps. v should be a [reflect.Value] of a map.
func interpolateMap(v reflect.Value) (err error) {
var kVal reflect.Value
var vVal reflect.Value
var newMap reflect.Value
var wg sync.WaitGroup
var numJobs int
var errChan chan error
var doneChan chan bool = make(chan bool, 1)
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
var t reflect.Type = v.Type()
var kind reflect.Kind = t.Kind()
if kind != reflect.Map {
err = errs.ErrBadType
return
}
if v.IsNil() || v.IsZero() || !v.IsValid() {
return
}
numJobs = v.Len()
errChan = make(chan error, numJobs)
wg.Add(numJobs)
newMap = reflect.MakeMap(v.Type())
for _, kVal = range v.MapKeys() {
vVal = v.MapIndex(kVal)
go func(key, val reflect.Value) {
var mapErr error
var newKey reflect.Value
var newVal reflect.Value
newKey = reflect.New(key.Type()).Elem()
newVal = reflect.New(val.Type()).Elem()
newKey.Set(key.Convert(newKey.Type()))
newVal.Set(val.Convert(newVal.Type()))
defer wg.Done()
// key
if key.Kind() == reflect.String {
if mapErr = interpolateStringReflect(newKey); mapErr != nil {
errChan <- mapErr
return
}
} else {
if mapErr = interpolateValue(newKey); mapErr != nil {
errChan <- mapErr
return
}
}
// value
if val.Kind() == reflect.String {
if mapErr = interpolateStringReflect(newVal); mapErr != nil {
errChan <- mapErr
return
}
} else {
if mapErr = interpolateValue(newVal); mapErr != nil {
errChan <- mapErr
return
}
}
newMap.SetMapIndex(newKey.Convert(key.Type()), newVal.Convert(key.Type()))
}(kVal, vVal)
}
go func() {
wg.Wait()
close(errChan)
doneChan <- true
}()
<-doneChan
for i := 0; i < numJobs; i++ {
if err = <-errChan; err != nil {
mErr.AddError(err)
err = nil
}
}
if !mErr.IsEmpty() {
err = mErr
return
}
v.Set(newMap.Convert(v.Type()))
return
}
// interpolateSlice is used by [Interpolate] for slices and arrays. v should be a [reflect.Value] of a slice/array.
func interpolateSlice(v reflect.Value) (err error) {
var wg sync.WaitGroup
var errChan chan error
var numJobs int
var doneChan chan bool = make(chan bool, 1)
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
var t reflect.Type = v.Type()
var kind reflect.Kind = t.Kind()
switch kind {
case reflect.Slice:
if v.IsNil() || v.IsZero() || !v.IsValid() {
return
}
case reflect.Array:
if v.IsZero() || !v.IsValid() {
return
}
default:
err = errs.ErrBadType
return
}
numJobs = v.Len()
errChan = make(chan error, numJobs)
wg.Add(numJobs)
for i := 0; i < v.Len(); i++ {
go func(idx int) {
var sErr error
defer wg.Done()
if v.Index(idx).Kind() == reflect.String {
if sErr = interpolateStringReflect(v.Index(idx)); sErr != nil {
errChan <- sErr
return
}
} else {
if sErr = interpolateValue(v.Index(idx)); sErr != nil {
errChan <- sErr
return
}
}
}(i)
}
go func() {
wg.Wait()
close(errChan)
doneChan <- true
}()
<-doneChan
for i := 0; i < numJobs; i++ {
if err = <-errChan; err != nil {
mErr.AddError(err)
err = nil
}
}
if !mErr.IsEmpty() {
err = mErr
return
}
return
}
// interpolateStringReflect is used for structs/nested strings using reflection.
func interpolateStringReflect(v reflect.Value) (err error) {
var strVal string
if v.Kind() != reflect.String {
err = errs.ErrBadType
return
}
if strVal, err = interpolateString(v.String()); err != nil {
return
}
v.Set(reflect.ValueOf(strVal).Convert(v.Type()))
return
}
// interpolateStruct is used by [Interpolate] for structs. v should be a [reflect.Value] of a struct.
func interpolateStruct(v reflect.Value) (err error) {
var field reflect.StructField
var fieldVal reflect.Value
var wg sync.WaitGroup
var errChan chan error
var numJobs int
var doneChan chan bool = make(chan bool, 1)
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
var t reflect.Type = v.Type()
var kind reflect.Kind = t.Kind()
if kind != reflect.Struct {
err = errs.ErrBadType
return
}
numJobs = v.NumField()
wg.Add(numJobs)
errChan = make(chan error, numJobs)
for i := 0; i < v.NumField(); i++ {
field = t.Field(i)
fieldVal = v.Field(i)
go func(f reflect.StructField, fv reflect.Value) {
var fErr error
defer wg.Done()
if fErr = interpolateStructField(f, fv); fErr != nil {
errChan <- fErr
return
}
}(field, fieldVal)
}
go func() {
wg.Wait()
close(errChan)
doneChan <- true
}()
<-doneChan
for i := 0; i < numJobs; i++ {
if err = <-errChan; err != nil {
mErr.AddError(err)
err = nil
}
}
if !mErr.IsEmpty() {
err = mErr
return
}
return
}
// interpolateStructField interpolates a struct field.
func interpolateStructField(field reflect.StructField, v reflect.Value) (err error) {
var parsedTagOpts map[string]bool
if !v.CanSet() {
return
}
// Skip if explicitly instructed to do so.
parsedTagOpts = structutils.TagToBoolMap(field, StructTagInterpolate, structutils.TagMapTrim)
if parsedTagOpts["-"] {
return
}
if v.Kind() == reflect.Ptr {
err = interpolateStructField(field, v.Elem())
} else {
err = interpolateValue(v)
}
return
}
// interpolateValue is a dispatcher for a reflect value.
func interpolateValue(v reflect.Value) (err error) {
var kind reflect.Kind = v.Kind()
switch kind {
case reflect.Ptr:
if v.IsNil() || v.IsZero() || !v.IsValid() {
return
}
v = v.Elem()
if err = interpolateValue(v); err != nil {
return
}
case reflect.String:
if err = interpolateStringReflect(v); err != nil {
return
}
return
case reflect.Slice, reflect.Array:
if err = interpolateSlice(v); err != nil {
}
case reflect.Map:
if err = interpolateMap(v); err != nil {
return
}
case reflect.Struct:
if err = interpolateStruct(v); err != nil {
return
}
}
return
}