Introduction

In this tutorial you will use CUE’s Go API to work with a CUE module dependency fetched from the Central Registry.

Along the way you will:

  • Login to the Central Registry, and authenticate the cue command
  • Create a CUE module that depends on an existing, well-known module
  • Use cue mod tidy to fetch and organise your module’s dependencies
  • Load your CUE using the Go API working in a modules-aware mode

Prerequisites

This tutorial uses the following version of CUE:

TERMINAL
$ cue version
cue version v0.12.0-0.dev.0.20241122172925-7d64696b0129
...

Set up the cue command

1

Authenticate the cue command with the Central Registry:

TERMINAL
$ cue login

Later in this tutorial the cue command will fetch a well-known module from the Central Registry, which requires authentication.

Create a CUE module

2

Initialize a new main CUE module in an empty directory:

TERMINAL
$ cue mod init an.example/config@v0

You won’t publish this module, so the name you give it is unimportant.

3

Create the file main.cue, holding the code for the main module:

main.cue
package config

import "github.com/cue-labs/examples/frostyconfig@v0"

config: frostyconfig.#Config & {
	appName: "alpha"
	port:    80
	features: logging: true
}

Your main module defines some concrete values for a configuration, constrained by the frostyconfig.#Config schema.

4

Ensure the CUE module is tidy:

TERMINAL
$ cue mod tidy

This fetches the frostyconfig module (and any dependencies it might have) from the Central Registry.

5

Export the configuration from your CUE module:

TERMINAL
$ cue export
{
    "config": {
        "appName": "alpha",
        "port": 80,
        "features": {
            "logging": true
        }
    }
}

This export shows that your CUE is valid and you can successfully use a dependency from the Central Registry.

Create a Go module and program

6

Initialize a Go module for your program:

TERMINAL
$ go mod init an.example/config
...

You won’t publish this module, so the name you give it is unimportant.

7

Create the file main.go containing this Go program:

main.go
package main

import (
	"fmt"
	"log"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/cuecontext"
	"cuelang.org/go/cue/load"
	"cuelang.org/go/mod/modconfig"
)

func main() {
	ctx := cuecontext.New()

	// Create a registry client. Passing a nil config
	// will give us client that behaves like the cue command.
	reg, err := modconfig.NewRegistry(nil)
	if err != nil {
		log.Fatal(err)
	}

	// Load the package from the current directory.
	// We don't need to specify a Config in this example.
	insts := load.Instances([]string{"."}, &load.Config{
		Registry: reg,
	})

	// The current directory just has one file without any build tags,
	// and that file belongs to the example package, so we get a single
	// instance as a result.
	v := ctx.BuildInstance(insts[0])
	if err := v.Err(); err != nil {
		log.Fatal(err)
	}

	// Lookup the 'config' field and print it out
	config := v.LookupPath(cue.ParsePath("config"))
	fmt.Println(config)
}

This program loads the CUE package in the current directory, and then prints a message based on the config field.

8

Add a dependency on cuelang.org/go and ensure the Go module is tidy:

TERMINAL
$ go get cuelang.org/go@v0.12.0-0.dev.0.20241122172925-7d64696b0129
...
$ go mod tidy
...

You can use @latest in place of the specific version mentioned here.

Run the Go program

9

Run the Go program:

TERMINAL
$ go run .
{
	appName: "alpha"
	port:    80
	features: {
		logging: true
	}
}

Summary

Well done - you’ve finished this tutorial! In completing it, you:

  • created a main module that depends on a well-known module from the Central Registry, and
  • used the Go API to load the main module, transparently using the module’s dependencies.