Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .github/workflows/cue.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ jobs:
cue_version: "v0.15.4"
- name: check that CUE schemas evaluate correctly
run: make cue-eval
- name: run the unit tests for CUE schemas
run: make cue-test

publish-module:
name: "publish module to Central Registry"
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ cue-gen:
done
cp -r cue.mod/gen/github.com/perses/spec/go/* cue/ && rm -r cue.mod/gen
find cue/ -name "*.cue" -exec sed -i 's/\"github.com\/perses\/spec\/go/\"github.com\/perses\/spec\/cue/g' {} \;

.PHONY: cue-test
cue-test:
@echo ">> Run the unit tests for CUE schemas"
$(GO) run ./scripts/test-cue/test-cue.go
41 changes: 41 additions & 0 deletions cue-test/datasource/http.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package datasource

import (
"github.com/perses/spec/cue/datasource/proxy/http"
)

myDirectSpec: #HTTPDatasourceSpec & {
directUrl: "http://localhost:8080"
}

myProxySpec: #HTTPDatasourceSpec & {
proxy: http.#Proxy & {
kind: "HTTPProxy"
spec: {
url: "https://prometheus.demo.prometheus.io"
allowedEndpoints: [
{
endpointPattern: "/api/v1/labels"
method: "POST"
},
{
endpointPattern: "/api/v1/series"
method: "POST"
},
]
}
}
}
29 changes: 29 additions & 0 deletions cue-test/datasource/sql.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package datasource

import (
"github.com/perses/spec/cue/datasource/proxy/sql"
)

mySQLProxySpec: #SQLDatasourceSpec & {
proxy: sql.#Proxy & {
kind: "SQLProxy"
spec: {
driver: "postgres"
host: "localhost:5432"
database: "mydb"
}
}
}
2 changes: 1 addition & 1 deletion cue/datasource/datasource_patch.cue
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ import (

#SQLDatasourceSpec: { proxy: sql.#Proxy }

#HTTPDatasourceSpec: { directUrl: common.#url } | { proxy: http.#Proxy }
#HTTPDatasourceSpec: { directUrl: common.#URL } | { proxy: http.#Proxy }
3 changes: 2 additions & 1 deletion cue/datasource/proxy/http/http_patch.cue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ package http
import (
"github.com/perses/spec/cue/common"
"github.com/perses/spec/cue/datasource/proxy"
)
)

#AllowedEndpoint: {
endpointPattern: string @go(EndpointPattern)
Expand All @@ -47,4 +47,5 @@ import (

#Proxy: proxy.#Proxy & {
kind: "HTTPProxy" @go(Kind)
spec: #Config @go(Spec)
}
1 change: 1 addition & 0 deletions cue/datasource/proxy/sql/sql_patch.cue
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@ import (

#Proxy: proxy.#Proxy & {
kind: "SQLProxy" @go(Kind)
spec: #Config @go(Spec)
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ module github.com/perses/spec
go 1.26.0

require (
github.com/sirupsen/logrus v1.9.4
github.com/stretchr/testify v1.11.1
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.13.0 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
162 changes: 162 additions & 0 deletions scripts/test-cue/test-cue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Copyright The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"fmt"
"os"
"os/exec"
"path/filepath"

"github.com/sirupsen/logrus"
)

// This script validates CUE schema packages against their corresponding test packages.
// It merges all .cue files within each package directory to properly handle imports and
// package-level definitions.

const (
schemasDir = "cue"
testDir = "cue-test"
)

// dirsInScope specifies which subdirectories under cue/ to validate
var dirsInScope = []string{"datasource"}

// NB: this function assume 1 dirInScope = 1 package. CUE allows multiple packages per dirInScope, but this is not used here.
func findPackages(basePath string, dirInScope string) ([]string, error) {
var packages []string
dirPath := filepath.Join(basePath, dirInScope)

// Include the root directory itself
packages = append(packages, dirInScope)

err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

if info.IsDir() && path != dirPath {
relPath, err := filepath.Rel(basePath, path)
if err != nil {
return err
}
packages = append(packages, relPath)
}

return nil
})

return packages, err
}

// vetPackage validates CUE files in schemaDir against test files in testDir.
// It collects all .cue files from both directories and runs `cue vet` on them together,
// allowing CUE to merge files in the same package and resolve imports properly.
// The command runs from schemasDir to ensure cue.mod/module.cue is accessible for imports.
func vetPackage(schemaDir, testDir string) error {
logrus.Debugf("Validating package %s against %s", schemaDir, testDir)

// Get list of all .cue files in both directories
schemaFiles, err := filepath.Glob(filepath.Join(schemaDir, "*.cue"))
if err != nil {
return fmt.Errorf("failed to glob schema files: %w", err)
}
testFiles, err := filepath.Glob(filepath.Join(testDir, "*.cue"))
if err != nil {
return fmt.Errorf("failed to glob test files: %w", err)
}

// Build command args with paths relative to schemasDir (cue/)
args := []string{"vet"}
for _, f := range schemaFiles {
rel, err := filepath.Rel(schemasDir, f)
if err != nil {
return fmt.Errorf("failed to get relative path for %s: %w", f, err)
}
args = append(args, rel)
}
for _, f := range testFiles {
// testFiles are in ../cue-test relative to schemasDir
rel, err := filepath.Rel(schemasDir, f)
if err != nil {
return fmt.Errorf("failed to get relative path for %s: %w", f, err)
}
args = append(args, rel)
}

cmd := exec.Command("cue", args...)
cmd.Dir = schemasDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to validate %s: %w", schemaDir, err)
}

return nil
}

func validateCueSchemas() error {
logrus.Debugf("Starting CUE files validation")

// Check if cue command is available
if _, err := exec.LookPath("cue"); err != nil {
return fmt.Errorf("cue command not found in PATH: %w", err)
}

validatedCount := 0
skippedCount := 0
errCount := 0

for _, dirInScope := range dirsInScope {
logrus.Debugf("Processing directory: %s", dirInScope)
packageDirs, err := findPackages(schemasDir, dirInScope)
if err != nil {
return fmt.Errorf("failed to find directories in %s/%s: %w", schemasDir, dirInScope, err)
}

for _, packageDir := range packageDirs {
schemaDir := filepath.Join(schemasDir, packageDir)
testDir := filepath.Join(testDir, packageDir)

// Check if corresponding test directory exists
if _, err := os.Stat(testDir); os.IsNotExist(err) {
logrus.Debugf("Skipping %s: test directory %s not found", schemaDir, testDir)
skippedCount++
continue
}

logrus.Infof("Validating package %s with test package %s", schemaDir, testDir)
if err := vetPackage(schemaDir, testDir); err != nil {
logrus.Errorf("Validation failed for %s: %v", schemaDir, err)
errCount++
}

validatedCount++
}
}
if errCount > 0 {
return fmt.Errorf("validation failed for %d file(s)", errCount)
}

logrus.Infof("CUE files validation completed: %d validated, %d skipped", validatedCount, skippedCount)
return nil
}

func main() {
if err := validateCueSchemas(); err != nil {
logrus.Fatal(err)
}
}
Loading