The ccConfig package provides a simple yet flexible configuration management system for the ClusterCockpit ecosystem. It allows components to load JSON configuration files with support for both inline configurations and external file references.
- Simple API: Just two main functions -
Init()to load andGetPackageConfig()to retrieve - File References: Support for splitting configuration across multiple files
- Flexible Structure: Organize config by component/package
- Helper Functions: Check for existence, list keys, and reset configuration
- Zero Dependencies: Uses only standard library (plus ccLogger)
All configuration files follow this JSON structure:
{
"main": {
"interval": "10s",
"debug": true
},
"database": {
"host": "localhost",
"port": 5432
},
"sinks-file": "./sinks.json"
}Each top-level key represents a configuration section that can be retrieved independently.
Keys ending with -file are treated specially. The value should be a string path to an external JSON file:
{
"receivers-file": "./receivers.json",
"sinks-file": "./sinks.json",
"main": {
"interval": "10s"
}
}In the example above:
receivers.jsoncontent will be available viaGetPackageConfig("receivers")sinks.jsoncontent will be available viaGetPackageConfig("sinks")- The inline
mainconfig is available viaGetPackageConfig("main")
Initializes the configuration system by loading a JSON configuration file.
ccconfig.Init("./config.json")Behavior:
- If the file doesn't exist, continues with empty configuration
- If the file is invalid JSON, terminates with fatal error
- Processes all
-filereferences and loads external files
Retrieves raw JSON configuration for a specific key.
rawConfig := ccconfig.GetPackageConfig("database")
if rawConfig == nil {
log.Fatal("database config not found")
}
var dbConfig DatabaseConfig
if err := json.Unmarshal(rawConfig, &dbConfig); err != nil {
log.Fatalf("invalid config: %v", err)
}Returns:
json.RawMessagecontaining the configuration datanilif the key doesn't exist (also logs an info message)
Checks whether a configuration key exists.
if ccconfig.HasKey("optional-metrics") {
config := ccconfig.GetPackageConfig("optional-metrics")
// ... use config
}Returns all available configuration keys.
keys := ccconfig.GetKeys()
for _, key := range keys {
fmt.Printf("Config section: %s\n", key)
}Clears all loaded configuration. Useful for testing.
ccconfig.Reset()
ccconfig.Init("test-config.json")package main
import (
"encoding/json"
"log"
ccconfig "github.com/ClusterCockpit/cc-lib/v2/ccConfig"
)
type AppConfig struct {
Interval string `json:"interval"`
Debug bool `json:"debug"`
Workers int `json:"workers"`
}
func main() {
// Initialize configuration
ccconfig.Init("./config.json")
// Check if configuration exists
if !ccconfig.HasKey("myapp") {
log.Fatal("myapp configuration section not found")
}
// Retrieve and parse configuration
rawConfig := ccconfig.GetPackageConfig("myapp")
var config AppConfig
if err := json.Unmarshal(rawConfig, &config); err != nil {
log.Fatalf("failed to parse config: %v", err)
}
// Use configuration
log.Printf("Starting with %d workers, interval: %s",
config.Workers, config.Interval)
}rawConfig := ccconfig.GetPackageConfig("component")
if rawConfig == nil {
// Handle missing configuration appropriately
log.Fatal("required configuration not found")
}type Config struct {
Interval string `json:"interval"`
Enabled bool `json:"enabled"`
// Use omitempty for optional fields
OptionalField string `json:"optional_field,omitempty"`
}Split large configurations into separate files:
{
"main": { ... },
"receivers-file": "./receivers.json",
"sinks-file": "./sinks.json",
"collectors-file": "./collectors.json"
}var config AppConfig
if err := json.Unmarshal(rawConfig, &config); err != nil {
log.Fatalf("invalid config: %v", err)
}
// Validate configuration values
if config.Interval == "" {
log.Fatal("interval must be specified")
}
if config.Workers < 1 {
log.Fatal("workers must be at least 1")
}func TestMyComponent(t *testing.T) {
ccconfig.Reset() // Clear any previous config
ccconfig.Init("./testdata/test-config.json")
// ... your tests
}Problem: GetPackageConfig() returns nil
Solutions:
- Check that
Init()was called with the correct file path - Verify the key name matches exactly (case-sensitive)
- For file references, ensure the
-filesuffix is in the config, not in your retrieval key - Use
GetKeys()to see all available keys
// Debug: Print all available keys
keys := ccconfig.GetKeys()
fmt.Printf("Available keys: %v\n", keys)Problem: Fatal error during Init() about invalid JSON
Solutions:
- Validate your JSON with a linter (e.g.,
jq . < config.json) - Check for trailing commas (not allowed in JSON)
- Ensure all strings use double quotes, not single quotes
- Verify file encoding is UTF-8
Problem: External file referenced via -file suffix not loading
Solutions:
- Check file path is relative to the current working directory
- Verify file exists and is readable
- Ensure the referenced file contains valid JSON
- Check logs for error messages from ccLogger
Problem: json.Unmarshal() fails when parsing retrieved config
Solutions:
- Ensure struct field types match JSON types
- Use JSON tags that match your config keys exactly
- Make sure struct fields are exported (start with capital letter)
- Enable debug logging to see the raw JSON being parsed
rawConfig := ccconfig.GetPackageConfig("myapp")
fmt.Printf("Raw config: %s\n", string(rawConfig)) // Debug print- Configuration is stored in a global map after initialization
- The package does not support hot-reloading (requires restart to pick up changes)
- File references are resolved at initialization time
- Missing config files (os.IsNotExist) are silently ignored, other errors are fatal
- The package is not thread-safe - ensure
Init()is called before concurrent access