spaceapid/config/config.go
Bennett Wetters 11046cb501 feat: Allow setting state->open to unknown via DELETE
The schema defines the absence of state->open to represent the unknown
state. Previously it was not possible to explicitly set this state via
the HTTP endpoints.

types.SpaceAPIState.Open is now an enum which is either unknown, closed,
or open. Custom IsZero() and MarshalJSON() methods on the type ensure
marshaling the value yields a boolean for open/closed and an empty byte
slice for unknown (resulting in the field being absent from the final
response).

The handlers.StateOpenDELETE() function has been added to expose this
behavior.

Switching to encoding/json/v2 required raising the minimum Go version in
go.mod to 1.27.

Closes: CCCHH/spaceapid!37
Fixes:  CCCHH/spaceapid#38
2026-10-09 00:30:26 +02:00

102 lines
2.5 KiB
Go

package config
import (
"bytes"
"encoding/json/v2"
"flag"
"fmt"
"log"
"os"
"slices"
"strings"
"git.hamburg.ccc.de/ccchh/spaceapid/types"
)
const (
envConfigPath = "SPACEAPID_CONFIG_PATH"
)
type pathsT []string
func (p *pathsT) String() string {
return fmt.Sprint(*p)
}
func (p *pathsT) Set(s string) error {
*p = make(pathsT, 0)
for _, path := range strings.Split(s, ",") {
*p = append(*p, path)
}
return nil
}
var (
flagConfigPaths pathsT
)
func init() {
flag.Var(&flagConfigPaths, "c", "Comma-separated list of config file paths. Parsed in order of appearance. Values get overwritten by files parsed later.")
}
func getConfigPaths() (paths pathsT) {
// Get paths from env variable and if env var present override cli flags
configPath, ok := os.LookupEnv(envConfigPath)
if ok {
if configPath == "" {
log.Fatalln("Env variable", envConfigPath, "is present but empty.")
}
_ = flagConfigPaths.Set(configPath)
}
for _, path := range flagConfigPaths {
_, err := os.Stat(path)
if err != nil {
log.Fatalln("Config file", path, "doesn't exist or is not accessible, error:", err)
}
paths = append(paths, path)
}
// If paths is still empty we are missing something
if len(paths) == 0 {
log.Fatalln("Error: Neither env:", envConfigPath, "nor cli flag was specified, unable to start without config.")
}
return
}
// ParseConfiguration returns the config from file
func ParseConfiguration() (conf SpaceapidConfig) {
log.Println("Parsing configuration files")
paths := getConfigPaths()
log.Println("Config files:", paths)
for _, path := range paths {
// Read file
file, err := os.ReadFile(path)
if err != nil {
log.Fatalln("Failed reading file:", err)
}
// Parse JSON
err = json.UnmarshalRead(bytes.NewReader(file), &conf, json.RejectUnknownMembers(true))
if err != nil {
log.Fatalln("Could not parse spaceapid config file:", err)
}
}
// Sanity check: Config should declare compatibility with current SpaceAPI message version
if !slices.Contains(conf.Response.APICompatibility, types.CurrentMessageVersion) {
log.Fatalln("Provided config doesn't specify compatibility with current API version", types.CurrentMessageVersion)
}
// Initialize fields for environment sensors
conf.Response.Sensors = make(map[string][]types.EnvironmentSensor)
for key, sensorConfigs := range conf.Dynamic.Sensors {
conf.Response.Sensors[key] = make([]types.EnvironmentSensor, len(sensorConfigs))
for i, sensorConfig := range sensorConfigs {
conf.Response.Sensors[key][i] = sensorConfig.SensorData
}
}
return
}