Skip to content

Commit e78bc54

Browse files
authored
Merge branch 'main' into nitijain/kace-303
2 parents 0af665d + 0b05a55 commit e78bc54

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+8720
-8468
lines changed

.github/dependabot.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ updates:
5959
interval: "weekly"
6060
labels: ["dependencies"]
6161

62+
- package-ecosystem: "gomod"
63+
directory: "/commons"
64+
target-branch: "main"
65+
schedule:
66+
interval: "weekly"
67+
labels: ["dependencies"]
68+
6269
# Fault Management
6370
- package-ecosystem: "gomod"
6471
directory: "/fault-quarantine-module"

.github/workflows/code-scanning.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ on:
2121
- "pull-request/[0-9]+"
2222
paths-ignore:
2323
- '**/*.md'
24+
- 'docs/**'
2425
- 'LICENSE'
2526
- '.github/ISSUE_TEMPLATE/**'
2627
- '.github/*.yaml'

.github/workflows/e2e-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ on:
2929
- "pull-request/[0-9]+"
3030
paths-ignore:
3131
- '**/*.md'
32+
- 'docs/**'
3233
- 'LICENSE'
3334
- '.github/ISSUE_TEMPLATE/**'
3435
- '.github/*.yaml'

.github/workflows/lint-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ on:
2121
- "pull-request/[0-9]+"
2222
paths-ignore:
2323
- '**/*.md'
24+
- 'docs/**'
2425
- 'LICENSE'
2526
- '.github/ISSUE_TEMPLATE/**'
2627
- '.github/*.yaml'

.github/workflows/publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ on:
2222
- main
2323
paths-ignore:
2424
- '**/*.md'
25+
- 'docs/**'
2526
- 'LICENSE'
2627
- '.github/ISSUE_TEMPLATE/**'
2728
- '.github/*.yaml'

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ on:
2020
- 'v*'
2121
paths-ignore:
2222
- '**/*.md'
23+
- 'docs/**'
2324
- 'LICENSE'
2425
- '.github/ISSUE_TEMPLATE/**'
2526
- '.github/*.yaml'

commons/Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Logger SDK Makefile
1+
# Commons Makefile
22
# Individual module build and test targets
33

44
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
@@ -40,8 +40,8 @@ all: lint-test ## Run lint-test (default target)
4040
# =============================================================================
4141
# MODULE NOTES
4242
# =============================================================================
43-
# This is a library module providing centralized logging initialization.
44-
# - Library module providing structured logging utilities
43+
# This is a library module providing common utilities for NVSentinel.
44+
# - Library module providing structured logging and configuration management
4545
# - No binary output or Docker builds
46-
# - Used by all nvsentinel modules for consistent logging setup
46+
# - Used by all nvsentinel modules for consistent logging and config loading
4747
# Run 'make help' to see available targets

commons/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.25
55
toolchain go1.25.3
66

77
require (
8+
github.com/BurntSushi/toml v1.5.0
89
github.com/prometheus/client_golang v1.23.2
910
github.com/stretchr/testify v1.11.1
1011
golang.org/x/sync v0.17.0

commons/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
2+
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
13
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
24
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
35
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=

commons/pkg/configmanager/env.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
// Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package configmanager
16+
17+
import (
18+
"fmt"
19+
"math"
20+
"os"
21+
"strconv"
22+
"strings"
23+
)
24+
25+
// GetEnvVar retrieves an environment variable and converts it to type T.
26+
// Type must be explicitly specified: GetEnvVar[int]("PORT", nil, nil)
27+
// If defaultValue is nil, the environment variable is required.
28+
// If defaultValue is non-nil, it will be used when the environment variable is not set.
29+
// Optional validator function validates the final value (from env or default).
30+
//
31+
// Supported types:
32+
// - int
33+
// - uint
34+
// - float64
35+
// - bool (accepts: "true" or "false", case-insensitive)
36+
// - string
37+
//
38+
// Example usage:
39+
//
40+
// // Required env var
41+
// port, err := configmanager.GetEnvVar[int]("PORT", nil, nil)
42+
//
43+
// // With default value
44+
// defaultTimeout := 30
45+
// timeout, err := configmanager.GetEnvVar[int]("TIMEOUT", &defaultTimeout, nil)
46+
//
47+
// // With default and validation
48+
// defaultMaxConn := 100
49+
// maxConn, err := configmanager.GetEnvVar[int]("MAX_CONN", &defaultMaxConn, func(v int) error {
50+
// if v <= 0 { return fmt.Errorf("must be positive") }
51+
// return nil
52+
// })
53+
//
54+
// // Required with validation
55+
// workers, err := configmanager.GetEnvVar[int]("WORKERS", nil, func(v int) error {
56+
// if v <= 0 { return fmt.Errorf("must be positive") }
57+
// return nil
58+
// })
59+
func GetEnvVar[T any](name string, defaultValue *T, validator func(T) error) (T, error) {
60+
var zero T
61+
62+
valueStr, exists := os.LookupEnv(name)
63+
if !exists {
64+
return handleMissingEnvVarWithDefault(name, defaultValue, validator)
65+
}
66+
67+
value, err := parseValue[T](valueStr)
68+
if err != nil {
69+
return zero, fmt.Errorf("error converting %s: %w", name, err)
70+
}
71+
72+
if validator != nil {
73+
if err := validator(value); err != nil {
74+
return zero, fmt.Errorf("validation failed for %s: %w", name, err)
75+
}
76+
}
77+
78+
return value, nil
79+
}
80+
81+
func handleMissingEnvVarWithDefault[T any](name string, defaultValue *T, validator func(T) error) (T, error) {
82+
var zero T
83+
84+
if defaultValue == nil {
85+
return zero, fmt.Errorf("environment variable %s is not set", name)
86+
}
87+
88+
if validator != nil {
89+
if err := validator(*defaultValue); err != nil {
90+
return zero, fmt.Errorf("validation failed for default value of %s: %w", name, err)
91+
}
92+
}
93+
94+
return *defaultValue, nil
95+
}
96+
97+
func parseValue[T any](valueStr string) (T, error) {
98+
var zero T
99+
100+
switch any(zero).(type) {
101+
case string:
102+
return any(valueStr).(T), nil
103+
case int:
104+
return parseAndConvert[T](parseInt(valueStr))
105+
case uint:
106+
return parseAndConvert[T](parseUint(valueStr))
107+
case float64:
108+
return parseAndConvert[T](parseFloat64(valueStr))
109+
case bool:
110+
return parseAndConvert[T](parseBool(valueStr))
111+
default:
112+
return zero, fmt.Errorf("unsupported type %T", zero)
113+
}
114+
}
115+
116+
func parseAndConvert[T any](value any, err error) (T, error) {
117+
var zero T
118+
if err != nil {
119+
return zero, err
120+
}
121+
122+
return any(value).(T), nil
123+
}
124+
125+
func parseInt(valueStr string) (int, error) {
126+
v, err := strconv.ParseInt(valueStr, 10, 64)
127+
if err != nil {
128+
return 0, err
129+
}
130+
131+
if v < math.MinInt || v > math.MaxInt {
132+
return 0, fmt.Errorf("value %d out of range for int type", v)
133+
}
134+
135+
return int(v), nil
136+
}
137+
138+
func parseUint(valueStr string) (uint, error) {
139+
v, err := strconv.ParseUint(valueStr, 10, 64)
140+
if err != nil {
141+
return 0, err
142+
}
143+
144+
if v > math.MaxUint {
145+
return 0, fmt.Errorf("value %d out of range for uint type", v)
146+
}
147+
148+
return uint(v), nil
149+
}
150+
151+
func parseFloat64(valueStr string) (float64, error) {
152+
return strconv.ParseFloat(valueStr, 64)
153+
}
154+
155+
// parseBool parses boolean values (accepts "true" or "false")
156+
func parseBool(valueStr string) (bool, error) {
157+
valueStr = strings.ToLower(strings.TrimSpace(valueStr))
158+
159+
switch valueStr {
160+
case "true":
161+
return true, nil
162+
case "false":
163+
return false, nil
164+
default:
165+
return false, fmt.Errorf("invalid boolean value: %s (must be 'true' or 'false')", valueStr)
166+
}
167+
}
168+
169+
// EnvVarSpec defines a specification for reading an environment variable.
170+
// All fields except Name are optional.
171+
//
172+
// Example usage:
173+
//
174+
// specs := []configmanager.EnvVarSpec{
175+
// {Name: "DATABASE_URL"}, // Required by default
176+
// {Name: "PORT", Optional: true, DefaultValue: "5432"}, // Optional with default
177+
// }
178+
// envVars, errors := configmanager.ReadEnvVars(specs)
179+
// if len(errors) > 0 {
180+
// return fmt.Errorf("missing required vars: %v", errors)
181+
// }
182+
type EnvVarSpec struct {
183+
Name string // Required: The environment variable name to read
184+
Optional bool // Optional: If true, env var is optional; if false, it's required (default: false/required)
185+
// DefaultValue is used when env var is not set.
186+
// Empty string defaults are treated as "no value" and excluded from results map.
187+
DefaultValue string
188+
}
189+
190+
// ReadEnvVars reads multiple environment variables based on the provided specifications.
191+
// Returns a map of environment variable names to their values and a slice of errors.
192+
// Environment variables are required by default unless Optional is set to true.
193+
//
194+
// Example usage:
195+
//
196+
// specs := []configmanager.EnvVarSpec{
197+
// {Name: "MONGODB_URI"}, // Required
198+
// {Name: "MONGODB_DATABASE_NAME"}, // Required
199+
// {Name: "MONGODB_PORT", Optional: true, DefaultValue: "27017"}, // Included with default
200+
// {Name: "DEBUG_MODE", Optional: true, DefaultValue: ""}, // NOT included (empty default)
201+
// }
202+
// envVars, errors := configmanager.ReadEnvVars(specs)
203+
// if len(errors) > 0 {
204+
// log.Fatalf("Missing required environment variables: %v", errors)
205+
// }
206+
// // Use the values
207+
// dbURI := envVars["MONGODB_URI"]
208+
// dbName := envVars["MONGODB_DATABASE_NAME"]
209+
// dbPort := envVars["MONGODB_PORT"] // Will be "27017" if not set
210+
func ReadEnvVars(specs []EnvVarSpec) (map[string]string, []error) {
211+
results := make(map[string]string)
212+
213+
var errors []error
214+
215+
for _, spec := range specs {
216+
value, exists := os.LookupEnv(spec.Name)
217+
218+
if !exists {
219+
defaultVal, err := handleMissingEnvVar(spec)
220+
if err != nil {
221+
errors = append(errors, err)
222+
}
223+
224+
// Only include non-empty defaults in results map to distinguish "not set" from "set to empty"
225+
if defaultVal != "" {
226+
results[spec.Name] = defaultVal
227+
}
228+
229+
continue
230+
}
231+
232+
results[spec.Name] = value
233+
}
234+
235+
return results, errors
236+
}
237+
238+
func handleMissingEnvVar(spec EnvVarSpec) (string, error) {
239+
if spec.Optional {
240+
return spec.DefaultValue, nil
241+
}
242+
243+
return "", fmt.Errorf("required environment variable %s is not set", spec.Name)
244+
}

0 commit comments

Comments
 (0)