diff --git a/cmd/timoni/bundle_build_test.go b/cmd/timoni/bundle_build_test.go index 7a7b2295..f6eeb0bb 100644 --- a/cmd/timoni/bundle_build_test.go +++ b/cmd/timoni/bundle_build_test.go @@ -2,8 +2,7 @@ package main import ( "fmt" - "os" - "path/filepath" + "io/ioutil" "strings" "testing" @@ -31,35 +30,66 @@ func Test_BundleBuild(t *testing.T) { )) g.Expect(err).ToNot(HaveOccurred()) - bundleData := fmt.Sprintf(` + bundleCue := fmt.Sprintf(` appName: string @timoni(env:string:TEST_BBUILD_NAME) bundle: { apiVersion: "v1alpha1" - name: "%[1]s" + name: string instances: { "\(appName)": { module: { - url: "oci://%[2]s" - version: "%[3]s" + url: "oci://%[1]s" + version: "%[2]s" } - namespace: "%[4]s" + namespace: "%[3]s" values: server: enabled: false values: domain: string @timoni(env:string:TEST_BBUILD_HOST) } backend: { module: { - url: "oci://%[2]s" - version: "%[3]s" + url: "oci://%[1]s" + version: "%[2]s" } - namespace: "%[4]s" + namespace: string values: client: enabled: bool @timoni(env:bool:TEST_BBUILD_ENABLED) } } } -`, bundleName, modURL, modVer, namespace) +`, modURL, modVer, namespace) - bundlePath := filepath.Join(t.TempDir(), "bundle.cue") - err = os.WriteFile(bundlePath, []byte(bundleData), 0644) + bundleData := bundleCue + fmt.Sprintf(` +bundle: name: "%[1]s" +bundle: instances: backend: namespace: "%[2]s" +`, bundleName, namespace) + + bundleJson := fmt.Sprintf(` +{ + "bundle": { + "name": "%[1]s" + } +} +`, bundleName) + + bundleYaml := fmt.Sprintf(` +bundle: + instances: + backend: + namespace: %[1]s +`, namespace) + + cuef, err := ioutil.TempFile(t.TempDir(), "*.cue") + g.Expect(err).ToNot(HaveOccurred()) + _, err = cuef.Write([]byte(bundleCue)) + g.Expect(err).ToNot(HaveOccurred()) + + yamlf, err := ioutil.TempFile(t.TempDir(), "*.yaml") + g.Expect(err).ToNot(HaveOccurred()) + _, err = yamlf.Write([]byte(bundleYaml)) + g.Expect(err).ToNot(HaveOccurred()) + + jsonf, err := ioutil.TempFile(t.TempDir(), "*.json") + g.Expect(err).ToNot(HaveOccurred()) + _, err = jsonf.Write([]byte(bundleJson)) g.Expect(err).ToNot(HaveOccurred()) t.Setenv("TEST_BBUILD_NAME", "frontend") @@ -68,10 +98,10 @@ bundle: { t.Run("builds instances from bundle", func(t *testing.T) { execCommands := map[string]func() (string, error){ - "using a file": func() (string, error) { + "using files": func() (string, error) { return executeCommand(fmt.Sprintf( - "bundle build -f %s -p main", - bundlePath, + "bundle build -f %s -f %s -f %s -p main", + cuef.Name(), yamlf.Name(), jsonf.Name(), )) }, "using stdin": func() (string, error) { diff --git a/internal/engine/bundle_builder.go b/internal/engine/bundle_builder.go index 6d8aa73c..e28b4f9d 100644 --- a/internal/engine/bundle_builder.go +++ b/internal/engine/bundle_builder.go @@ -17,20 +17,17 @@ limitations under the License. package engine import ( - "bytes" - "errors" "fmt" "os" "path/filepath" - "io" "cuelang.org/go/cue" - "cuelang.org/go/cue/build" + "cuelang.org/go/cue/ast" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/load" + "cuelang.org/go/cue/parser" "cuelang.org/go/encoding/json" "cuelang.org/go/encoding/yaml" - cp "github.com/otiai10/copy" apiv1 "github.com/stefanprodan/timoni/api/v1alpha1" ) @@ -75,23 +72,45 @@ func (b *BundleBuilder) InitWorkspace(workspace string) error { var files []string for i, file := range b.files { _, fn := filepath.Split(file) - dstFile := filepath.Join(workspace, fmt.Sprintf("%v.%s", i, fn)) - files = append(files, dstFile) - if err := cp.Copy(file, dstFile); err != nil { - return err + content, err := os.ReadFile(file) + if err != nil { + return fmt.Errorf("failed to read %s: %w", fn, err) } - } - for _, f := range files { - _, fn := filepath.Split(f) - data, err := b.injector.Inject(f) + var node ast.Node + + ext := filepath.Ext(file) + switch ext { + case ".yaml", ".yml": + node, err = yaml.Extract(file, content) + if err != nil { + return fmt.Errorf("failed to extract %s: %w", fn, err) + } + case ".json": + node, err = json.Extract(file, content) + if err != nil { + return fmt.Errorf("failed to extract %s: %w", fn, err) + } + case ".cue": + node, err = parser.ParseFile(file, content, parser.ParseComments) + if err != nil { + return fmt.Errorf("failed to parse %s: %w", fn, err) + } + default: + return fmt.Errorf("unsupported file extension %s", ext) + } + + data, err := b.injector.InjectNode(node) if err != nil { return fmt.Errorf("failed to inject %s: %w", fn, err) } - if err := os.WriteFile(f, data, os.ModePerm); err != nil { - return fmt.Errorf("failed to inject %s: %w", fn, err) + dstFile := filepath.Join(workspace, fmt.Sprintf("%v.%s.cue", i, fn)) + if err := os.WriteFile(dstFile, data, os.ModePerm); err != nil { + return fmt.Errorf("failed to write %s: %w", fn, err) } + + files = append(files, dstFile) } schemaFile := filepath.Join(workspace, fmt.Sprintf("%v.schema.cue", len(b.files)+1)) @@ -124,26 +143,6 @@ func (b *BundleBuilder) Build() (cue.Value, error) { } v := b.ctx.BuildInstance(inst) - for _, f := range inst.OrphanedFiles { - switch f.Encoding { - case build.YAML: - a, err := yaml.Extract(f.Filename, f.Source) - if err != nil { - return value, err - } - v = v.Unify(b.ctx.BuildFile(a)) - case build.JSON: - src, err := readSource(f.Filename, f.Source) - if err != nil { - return value, err - } - exp, err := json.Extract(f.Filename, src) - if err != nil { - return value, err - } - v = v.Unify(b.ctx.BuildExpr(exp)) - } - } if v.Err() != nil { return value, v.Err() } @@ -155,30 +154,6 @@ func (b *BundleBuilder) Build() (cue.Value, error) { return v, nil } -func readSource(filename string, src interface{}) ([]byte, error) { - if src != nil { - switch s := src.(type) { - case string: - return []byte(s), nil - case []byte: - return s, nil - case *bytes.Buffer: - // is io.Reader, but src is already available in []byte form - if s != nil { - return s.Bytes(), nil - } - case io.Reader: - var buf bytes.Buffer - if _, err := io.Copy(&buf, s); err != nil { - return nil, err - } - return buf.Bytes(), nil - } - return nil, errors.New("invalid source") - } - return os.ReadFile(filename) -} - // GetBundle returns a Bundle from the bundle CUE value. func (b *BundleBuilder) GetBundle(v cue.Value) (*Bundle, error) { bundleNameValue := v.LookupPath(cue.ParsePath(apiv1.BundleName.String())) diff --git a/internal/engine/injector.go b/internal/engine/injector.go index a73665ec..e0eb96fa 100644 --- a/internal/engine/injector.go +++ b/internal/engine/injector.go @@ -54,6 +54,10 @@ func (in *Injector) Inject(src string) ([]byte, error) { return nil, err } + return in.InjectNode(tree) +} + +func (in *Injector) InjectNode(tree ast.Node) ([]byte, error) { output, err := in.injectFromEnv(tree) if err != nil { return nil, err @@ -67,7 +71,7 @@ func (in *Injector) Inject(src string) ([]byte, error) { return data, nil } -func (in *Injector) injectFromEnv(tree *ast.File) (ast.Node, error) { +func (in *Injector) injectFromEnv(tree ast.Node) (ast.Node, error) { var re error f := func(c astutil.Cursor) bool { n := c.Node()