Cue for Go program configurations

Share on:

Cue is a nifty language with useful features beyond what you get from YAML or JSON for config files. Really, there’s just a ton of stuff there, and the cue tool is designed to allow you to write Cue files that then get converted to YAML or JSON for use by programs expecting those, well, inferior formats.

I wanted to try Cue out as a config format for a tool that I’ve been working on. Cue is still early (v0.2.2), so I am a little worried about compatibility, but this tool has exactly one user (me), making it a good testbed.

Cue’s docs for using it from Go didn’t quite provide the detail I needed. It’s actually pretty easy to use, if you know how the bits glue together. I can’t guarantee this is the right way to do it, but it worked for me.

My setup for Cue involved a cue.mod in the top-level directory of my project that contained the schema, a config directory under that with shared config entries, and then a couple of directories under there with more specific configurations. I could just collapse that down to two levels.

To use the code below, I just call LoadConfig("./config/specific") and Cue handles the rest!

import (
	"cuelang.org/go/cue"
	"cuelang.org/go/cue/load"
	"fmt"
)

// MyConfig will ultimately contain the values from the Cue
// configuration files. In this case, it's just looking for a string
// called A.
type MyConfig struct {
	A string
}

// LoadConfig loads the Cue config files, starting in the
// dirname directory.
func LoadConfig(dirname string) (*MyConfig, error) {
	cueConfig := &load.Config{
		ModuleRoot: "",
		Module:     "YourCueModuleName",
		Package:    "NameOfYourCuePackage",
		Dir:        dirname,
		BuildTags:  []string{},
		Tests:      false,
		Tools:      false,
		DataFiles:  false,
		StdRoot:    "",
	}

	buildInstances := load.Instances([]string{}, cueConfig)
	// I'm not sure which circumstances would cause there to be multiples of
	// these, but that's not what I want in this usage.
	if len(buildInstances) != 1 {
		panic(fmt.Sprintf("Unexpectedly got %d build instances of configs\n", len(buildInstances)))
	}
	runtimeInstances := cue.Build(buildInstances)
	if len(runtimeInstances) != 1 {
		panic(fmt.Sprintf("Unexpectedly got %d runtime instances of configs\n", len(runtimeInstances)))
	}
	instance := runtimeInstances[0]
	var config MyConfig

	// This is where the result is validated
	err := instance.Value().Decode(&config)
	if err != nil {
		return nil, err
	}
	return &config, nil
}