Skip to content

Commit

Permalink
refactor: uses cue-helper library for replacing CUE values (#170)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmgilman authored Jan 26, 2024
1 parent 0c3e1b0 commit 902956e
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 119 deletions.
10 changes: 6 additions & 4 deletions tools/updater/cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package main

// cspell: words alecthomas cuelang cuecontext cuectx existingfile Timoni nolint
// cspell: words afero alecthomas cuelang cuecontext cuectx existingfile mheers Timoni nolint

import (
"os"
Expand All @@ -10,6 +10,8 @@ import (
"cuelang.org/go/cue/format"
"github.com/alecthomas/kong"
"github.com/input-output-hk/catalyst-ci/tools/updater/pkg"
ch "github.com/mheers/cue-helper/pkg/value"
"github.com/spf13/afero"
)

var cli struct {
Expand All @@ -24,17 +26,17 @@ func main() {
kong.Description("A helper tool for modifying CUE files to override arbitrary values. Useful for updating Timoni bundles."))

cuectx := cuecontext.New()
v, err := pkg.ReadFile(cuectx, cli.BundleFile)
v, err := pkg.ReadFile(cuectx, cli.BundleFile, afero.NewOsFs())
ctx.FatalIfErrorf(err)

if !v.LookupPath(cue.ParsePath(cli.Path)).Exists() {
ctx.Fatalf("path %q does not exist", cli.Path)
}

v, err = pkg.FillPathOverride(cuectx, v, cli.Path, cli.Value)
v, err = ch.Replace(v, cli.Path, cli.Value)
ctx.FatalIfErrorf(err)

node := v.Syntax(cue.Final(), cue.Concrete(true))
node := v.Syntax(cue.Final(), cue.Concrete(true), cue.Docs(true))
src, err := format.Node(node)
ctx.FatalIfErrorf(err)

Expand Down
13 changes: 9 additions & 4 deletions tools/updater/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@ go 1.20
require (
cuelang.org/go v0.7.0
github.com/alecthomas/kong v0.8.1
github.com/onsi/ginkgo/v2 v2.15.0
github.com/onsi/gomega v1.31.0
github.com/mheers/cue-helper v0.0.0-20231214081257-a325036f9a0d
github.com/onsi/ginkgo/v2 v2.9.2
github.com/onsi/gomega v1.27.5
github.com/spf13/afero v1.11.0
)

require (
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/kubevela/workflow v0.6.0 // indirect
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
30 changes: 20 additions & 10 deletions tools/updater/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
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/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
Expand All @@ -24,29 +26,37 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kubevela/workflow v0.6.0 h1:fYXviOYD5zqHs3J61tNbM4HZ85EcZlPm7Fyz8Q5o9Fk=
github.com/kubevela/workflow v0.6.0/go.mod h1:sjLcYqKHKeCQ+w77gijoNILwIShJKnCU+e3q7ETtZGI=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/mheers/cue-helper v0.0.0-20231214081257-a325036f9a0d h1:JZsFf1WWJjBq7swih1HeWbmnh5uKZ2Ol2HSZ8o6kcK8=
github.com/mheers/cue-helper v0.0.0-20231214081257-a325036f9a0d/go.mod h1:TvPPtiz/noXLe+NDP3XJLg8Y0ROt3m6rVlqUYlLuba0=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto=
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE=
github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk=
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/gomega v1.27.5 h1:T/X6I0RNFw/kTqgfkZPcQ5KU6vCnWNBGdtrIx2dpGeQ=
github.com/onsi/gomega v1.27.5/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 h1:sadMIsgmHpEOGbUs6VtHBXRR1OHevnj7hLx9ZcdNGW4=
github.com/rogpeppe/go-internal v1.11.1-0.20231026093722-fa6a31e0812c h1:fPpdjePK1atuOg28PXfNSqgwf9I/qD1Hlo39JFwKBXk=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
Expand All @@ -57,7 +67,7 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
57 changes: 4 additions & 53 deletions tools/updater/pkg/cue.go
Original file line number Diff line number Diff line change
@@ -1,46 +1,17 @@
package pkg

// cspell: words cuelang
// cspell: words afero cuelang

import (
"encoding/json"
"fmt"
"os"
"strings"

"cuelang.org/go/cue"
"github.com/spf13/afero"
)

// FillPathOverride is like cue.Value.FillPath, but it allows you to override concrete values.
// The default behavior of CUE is to support immutable values, so you can't normally override a concrete value.
// This function first converts the cue.Value to JSON, then to a map, and then modifies the map.
// Finally, it converts the map back to JSON and then to a cue.Value.
func FillPathOverride(ctx *cue.Context, v cue.Value, path string, value interface{}) (cue.Value, error) {
j, err := v.MarshalJSON()
if err != nil {
return cue.Value{}, fmt.Errorf("failed to marshal cue.Value to JSON: %w", err)
}

var data map[string]interface{}
if err := json.Unmarshal(j, &data); err != nil {
return cue.Value{}, fmt.Errorf("failed to unmarshal JSON to map: %w", err)
}

if err := setField(data, path, value); err != nil {
return cue.Value{}, fmt.Errorf("failed to set field %q to value %v: %w", path, value, err)
}

modifiedJSON, err := json.Marshal(data)
if err != nil {
return cue.Value{}, fmt.Errorf("failed to marshal map to JSON: %w", err)
}

return ctx.CompileBytes(modifiedJSON), nil
}

// ReadFile reads a CUE file and returns a cue.Value.
func ReadFile(ctx *cue.Context, path string) (cue.Value, error) {
contents, err := os.ReadFile(path)
func ReadFile(ctx *cue.Context, path string, os afero.Fs) (cue.Value, error) {
contents, err := afero.ReadFile(os, path)
if err != nil {
return cue.Value{}, fmt.Errorf("failed to read file %q: %w", path, err)
}
Expand All @@ -52,23 +23,3 @@ func ReadFile(ctx *cue.Context, path string) (cue.Value, error) {

return v, nil
}

// setField sets the value at the given path in a map, creating nested maps as necessary.
func setField(m map[string]interface{}, path string, value interface{}) error {
parts := strings.Split(path, ".")
for i, part := range parts {
if i == len(parts)-1 {
m[part] = value
} else {
if _, ok := m[part]; !ok {
m[part] = make(map[string]interface{})
}
var ok bool
m, ok = m[part].(map[string]interface{})
if !ok {
return fmt.Errorf("invalid path: %s", path)
}
}
}
return nil
}
69 changes: 21 additions & 48 deletions tools/updater/pkg/cue_test.go
Original file line number Diff line number Diff line change
@@ -1,68 +1,41 @@
package pkg_test

// cspell: words cuelang cuecontext
// cspell: words afero cuelang cuecontext

import (
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"github.com/input-output-hk/catalyst-ci/tools/updater/pkg"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/spf13/afero"
)

var _ = Describe("Cue", func() {
Describe("FillPathOverride", func() {
var path, str string
Describe("ReadFile", func() {
var os afero.Fs

When("given a nested CUE source", func() {
When("the path exists", func() {
BeforeEach(func() {
path = "bundles.instances.module.values.image.tag"
str = `
bundle: {
instances: {
module: {
values: {
image: tag: "test"
}
}
}
}
`
})

It("should override the value at the given path", func() {
ctx := cuecontext.New()
v := ctx.CompileString(str)
BeforeEach(func() {
os = afero.NewMemMapFs()
})

v, err := pkg.FillPathOverride(ctx, v, path, "test1")
Expect(err).ToNot(HaveOccurred())
When("the file exists", func() {
It("returns a cue.Value", func() {
err := afero.WriteFile(os, "foo.cue", []byte("foo: 1"), 0644)
Expect(err).ToNot(HaveOccurred())

result, err := v.LookupPath(cue.ParsePath(path)).String()
Expect(err).ToNot(HaveOccurred())
Expect(result).To(Equal("test1"))
})
ctx := cuecontext.New()
v, err := pkg.ReadFile(ctx, "foo.cue", os)
Expect(err).ToNot(HaveOccurred())
Expect(v).ToNot(BeNil())
})
})

When("the path does not include structs", func() {
BeforeEach(func() {
path = "bundles.instances.module.values.image.tag"
str = `
bundles: {
instances: {
module: "test"
}
}
`
})

It("should return an error", func() {
ctx := cuecontext.New()
v := ctx.CompileString(str)
When("the file does not exist", func() {
It("returns an error", func() {
ctx := cuecontext.New()

_, err := pkg.FillPathOverride(ctx, v, path, "test1")
Expect(err).To(HaveOccurred())
})
_, err := pkg.ReadFile(ctx, "foo.cue", os)
Expect(err).To(HaveOccurred())
})
})
})
Expand Down

0 comments on commit 902956e

Please sign in to comment.