Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4a97c60
feat: add backend caching for AMT API endpoints
nmgaston Jan 23, 2026
3287fdc
Merge branch 'main' into backendCaching
nmgaston Jan 23, 2026
177381f
fix: remove unused time imports from devices package
nmgaston Jan 23, 2026
722eb74
fix: resolve golangci-lint issues (godot, nlreturn, wsl_v5)
nmgaston Jan 23, 2026
b2c3fc7
fix: add periods to remaining godoc comments in keys.go
nmgaston Jan 23, 2026
6868be0
feat: add combined KVM initialization endpoint
nmgaston Jan 23, 2026
e96a3e4
feat: add Prometheus and Grafana to docker-compose
nmgaston Jan 23, 2026
2f48991
chore: format files
nmgaston Jan 27, 2026
a079f42
fix: add missing methods to ws/v1 Feature interface and regenerate mocks
nmgaston Jan 27, 2026
7bc9d8d
fix: create prometheus.yml in CI workflow before docker-compose
nmgaston Jan 27, 2026
f233389
chore: format files
nmgaston Jan 27, 2026
bdd17d5
fix: replace magic number with constant for KVM init cache TTL
nmgaston Jan 27, 2026
1b62eff
fix: only start required services in CI, exclude prometheus/grafana
nmgaston Jan 27, 2026
dfddae5
refactor: remove prometheus and grafana from docker-compose
nmgaston Jan 27, 2026
0e166fb
revert: remove unnecessary quotes from AUTH_DISABLED value
nmgaston Jan 27, 2026
c7a13bc
Merge branch 'main' into backendCaching
nmgaston Jan 30, 2026
4330058
chore: remove caching debug statements
nmgaston Feb 3, 2026
51de2c5
Merge branch 'backendCaching' of https://github.com/nmgaston/console …
nmgaston Feb 3, 2026
aac928d
chore: fix lint issue
nmgaston Feb 3, 2026
21bd4c6
feat: implement backend caching with robfig/go-cache for improved per…
nmgaston Feb 4, 2026
1856a09
Merge branch 'main' into backendCaching
nmgaston Feb 5, 2026
1088fb2
feat: implement backend caching with robfig/go-cache for improved per…
nmgaston Feb 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ var ConsoleConfig *Config

const defaultHost = "localhost"

// Cache TTL validation limits.
const (
MaxCacheTTL = 5 * time.Minute // Maximum cache TTL (5 minutes)
MaxPowerStateTTL = 1 * time.Minute // Maximum power state TTL (1 minute)
MinCacheTTL = 0 // Minimum (0 = disabled)
)

// Cache validation errors.
var (
ErrCacheTTLNegative = errors.New("cache ttl cannot be negative")
ErrCacheTTLExceedsMax = errors.New("cache ttl exceeds maximum allowed value of 5 minutes")
ErrCachePowerStateTTLNegative = errors.New("cache powerstate_ttl cannot be negative")
ErrCachePowerStateTTLExceedsMax = errors.New("cache powerstate_ttl exceeds maximum allowed value of 1 minute")
)

type (
// Config -.
Config struct {
Expand All @@ -27,6 +42,7 @@ type (
EA `yaml:"ea"`
Auth `yaml:"auth"`
UI `yaml:"ui"`
Cache `yaml:"cache"`
}

// App -.
Expand Down Expand Up @@ -110,8 +126,36 @@ type (
UI struct {
ExternalURL string `yaml:"externalUrl" env:"UI_EXTERNAL_URL"`
}

// Cache -.
Cache struct {
TTL time.Duration `yaml:"ttl" env:"CACHE_TTL"` // Cache TTL for features/KVM (set to 0 to disable caching, max 5 minutes)
PowerStateTTL time.Duration `yaml:"powerstate_ttl" env:"CACHE_POWERSTATE_TTL"` // Power state TTL (typically shorter since it changes more frequently, max 1 minute)
}
)

// ValidateCacheConfig validates cache configuration values for security.
// Returns error if values are negative or exceed maximum limits.
func (c *Config) ValidateCacheConfig() error {
if c.TTL < MinCacheTTL {
return ErrCacheTTLNegative
}

if c.TTL > MaxCacheTTL {
return ErrCacheTTLExceedsMax
}

if c.PowerStateTTL < MinCacheTTL {
return ErrCachePowerStateTTLNegative
}

if c.PowerStateTTL > MaxPowerStateTTL {
return ErrCachePowerStateTTLExceedsMax
}

return nil
}

// getPreferredIPAddress detects the most likely candidate IP address for this machine.
// It prefers non-loopback IPv4 addresses and excludes link-local addresses.
func getPreferredIPAddress() string {
Expand Down Expand Up @@ -197,6 +241,10 @@ func defaultConfig() *Config {
UI: UI{
ExternalURL: "",
},
Cache: Cache{
TTL: 30 * time.Second,
PowerStateTTL: 5 * time.Second,
},
}
}

Expand Down Expand Up @@ -279,5 +327,10 @@ func NewConfig() (*Config, error) {
return nil, err
}

// Validate cache configuration
if err := ConsoleConfig.ValidateCacheConfig(); err != nil {
return nil, err
}

return ConsoleConfig, nil
}
9 changes: 9 additions & 0 deletions config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,13 @@ ui:
# - Ignored: When building without 'noui' tag (embedded UI is served normally)
# Example: https://ui.example.com
externalUrl: ""
cache:
# ttl: Cache time-to-live for AMT features and KVM data
# Valid range: 0 (disabled) to 5 minutes (300s)
# Recommended: 30s for balance between performance and freshness
ttl: 30s
# powerstate_ttl: Separate TTL for power state (changes more frequently)
# Valid range: 0 (disabled) to 1 minute (60s)
# Recommended: 5s since power state changes during operations
powerstate_ttl: 5s

84 changes: 84 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -107,3 +108,86 @@ postgres:
assert.Equal(t, 10, cfg.PoolMax)
assert.Equal(t, "postgres://envuser:envpassword@localhost:5432/envdb", cfg.DB.URL)
}

func TestValidateCacheConfig(t *testing.T) {
t.Parallel()

tests := []struct {
name string
cache Cache
expectedError string
}{
{
name: "valid default values",
cache: Cache{
TTL: 30 * time.Second,
PowerStateTTL: 5 * time.Second,
},
expectedError: "",
},
{
name: "valid disabled cache",
cache: Cache{
TTL: 0,
PowerStateTTL: 0,
},
expectedError: "",
},
{
name: "valid maximum values",
cache: Cache{
TTL: MaxCacheTTL,
PowerStateTTL: MaxPowerStateTTL,
},
expectedError: "",
},
{
name: "negative ttl",
cache: Cache{
TTL: -1 * time.Second,
PowerStateTTL: 5 * time.Second,
},
expectedError: "cache ttl cannot be negative",
},
{
name: "negative powerstate_ttl",
cache: Cache{
TTL: 30 * time.Second,
PowerStateTTL: -1 * time.Second,
},
expectedError: "cache powerstate_ttl cannot be negative",
},
{
name: "ttl exceeds maximum",
cache: Cache{
TTL: 6 * time.Minute,
PowerStateTTL: 5 * time.Second,
},
expectedError: "cache ttl exceeds maximum allowed value of 5 minutes",
},
{
name: "powerstate_ttl exceeds maximum",
cache: Cache{
TTL: 30 * time.Second,
PowerStateTTL: 2 * time.Minute,
},
expectedError: "cache powerstate_ttl exceeds maximum allowed value of 1 minute",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

cfg := &Config{Cache: tt.cache}

err := cfg.ValidateCacheConfig()
if tt.expectedError == "" {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedError)
}
})
}
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/Masterminds/squirrel v1.5.4
github.com/coreos/go-oidc/v3 v3.17.0
github.com/device-management-toolkit/go-wsman-messages/v2 v2.36.1
github.com/gin-contrib/cache v1.4.1
github.com/gin-contrib/cors v1.7.6
github.com/gin-contrib/pprof v1.5.3
github.com/gin-gonic/gin v1.11.0
Expand All @@ -29,13 +30,15 @@ require (

require (
al.essio.dev/pkg/shellescape v1.5.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gomodule/redigo v1.9.2 // indirect
github.com/gorilla/schema v1.4.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
Expand All @@ -47,6 +50,7 @@ require (
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/memcachier/mc/v3 v3.0.3 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
Expand All @@ -56,6 +60,7 @@ require (
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.57.0 // indirect
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/woodsbury/decimal128 v1.4.0 // indirect
github.com/zalando/go-keyring v0.2.6 // indirect
Expand Down
Loading
Loading