Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bundle] Fix local module refs for multi-cluster #429

Merged
merged 4 commits into from
Oct 22, 2024
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: 1 addition & 1 deletion cmd/timoni/bundle_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func runBundleApplyCmd(cmd *cobra.Command, _ []string) error {
return describeErr(workspace, "failed to parse bundle", err)
}

v, err := bm.Build()
v, err := bm.Build(workspace)
if err != nil {
return describeErr(tmpDir, "failed to build bundle", err)
}
Expand Down
162 changes: 162 additions & 0 deletions cmd/timoni/bundle_apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

"github.com/google/go-containerregistry/pkg/crane"
. "github.com/onsi/gomega"
cp "github.com/otiai10/copy"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -592,3 +593,164 @@ runtime: {
g.Expect(err.Error()).To(ContainSubstring("no cluster found"))
})
}

func Test_BundleApply_Runtime_LocalModule_WithRelativePath(t *testing.T) {
g := NewWithT(t)

bundleName := "my-bundle"
modPath := "testdata/module"
namespace := rnd("my-ns", 5)
modVer := "1.0.0"

bundleCue := fmt.Sprintf(`
bundle: {
apiVersion: "v1alpha1"
name: "%[1]s"
instances: {
app: {
module: {
url: "file://%[2]s"
version: "%[3]s"
}
namespace: "%[4]s"
}
}
}
`, bundleName, modPath, modVer, namespace)

// Copy testdata/module/ to /tmpdir/testdata/module/
// This is needed because `bundlePath` below will be `tmpdir/bundle.cue`
// which means the `file://./testdata/module/` will be normalized to /tmpdir/testdata/module
bundleTmpDir := t.TempDir()
g.Expect(cp.Copy(modPath, filepath.Join(bundleTmpDir, modPath))).To(Succeed())

bundlePath := filepath.Join(bundleTmpDir, "bundle.cue")
err := os.WriteFile(bundlePath, []byte(bundleCue), 0644)
g.Expect(err).ToNot(HaveOccurred())

runtimeCue := `
runtime: {
apiVersion: "v1alpha1"
name: "test"
clusters: {
"test": {
group: "testing"
kubeContext: "envtest"
}
"staging": {
group: "staging"
kubeContext: "envtest"
}
}
}
`

runtimePath := filepath.Join(t.TempDir(), "runtime.cue")
err = os.WriteFile(runtimePath, []byte(runtimeCue), 0644)
g.Expect(err).ToNot(HaveOccurred())

t.Run("creates instances from concrete bundle file and concrete runtime file", func(t *testing.T) {
g := NewWithT(t)

cmd := fmt.Sprintf("bundle apply -p main --wait -f=%s -r=%s",
bundlePath,
runtimePath,
)

output, err := executeCommand(cmd)
g.Expect(err).ToNot(HaveOccurred())
t.Log("\n", output)
})

t.Run("creates instances from stdin bundle and concrete runtime file", func(t *testing.T) {
g := NewWithT(t)

cmd := fmt.Sprintf("bundle apply -p main --wait -f- -r=%s",
runtimePath,
)

output, err := executeCommandWithIn(cmd, strings.NewReader(bundleCue))
g.Expect(err).To(HaveOccurred())
t.Log("\n", output)
})
}

func Test_BundleApply_Runtime_LocalModule_WithAbsolutePath(t *testing.T) {
g := NewWithT(t)

// Copy testdata/module/ to /tmpdir/testdata/module/
// modPath will become an absolute path `/tmpdir/testdata/module/`
bundleTmpDir := t.TempDir()
g.Expect(cp.Copy("testdata/module", filepath.Join(bundleTmpDir, "testdata/module"))).To(Succeed())

bundleName := "my-bundle"
modPath := filepath.Join(bundleTmpDir, "testdata/module")
namespace := rnd("my-ns", 5)
modVer := "1.0.0"

bundleCue := fmt.Sprintf(`
bundle: {
apiVersion: "v1alpha1"
name: "%[1]s"
instances: {
app: {
module: {
url: "file://%[2]s"
version: "%[3]s"
}
namespace: "%[4]s"
}
}
}
`, bundleName, modPath, modVer, namespace)

bundlePath := filepath.Join(bundleTmpDir, "bundle.cue")
err := os.WriteFile(bundlePath, []byte(bundleCue), 0644)
g.Expect(err).ToNot(HaveOccurred())

runtimeCue := `
runtime: {
apiVersion: "v1alpha1"
name: "test"
clusters: {
"test": {
group: "testing"
kubeContext: "envtest"
}
"staging": {
group: "staging"
kubeContext: "envtest"
}
}
}
`

runtimePath := filepath.Join(t.TempDir(), "runtime.cue")
err = os.WriteFile(runtimePath, []byte(runtimeCue), 0644)
g.Expect(err).ToNot(HaveOccurred())

t.Run("creates instances from concrete bundle file and concrete runtime file", func(t *testing.T) {
g := NewWithT(t)

cmd := fmt.Sprintf("bundle apply -p main --wait -f=%s -r=%s",
bundlePath,
runtimePath,
)

output, err := executeCommand(cmd)
g.Expect(err).ToNot(HaveOccurred())
t.Log("\n", output)
})

t.Run("creates instances from stdin bundle and concrete runtime file", func(t *testing.T) {
g := NewWithT(t)

cmd := fmt.Sprintf("bundle apply -p main --wait -f- -r=%s",
runtimePath,
)

output, err := executeCommandWithIn(cmd, strings.NewReader(bundleCue))
g.Expect(err).ToNot(HaveOccurred())
t.Log("\n", output)
})
}
2 changes: 1 addition & 1 deletion cmd/timoni/bundle_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func runBundleBuildCmd(cmd *cobra.Command, _ []string) error {
return describeErr(tmpDir, "failed to parse bundle", err)
}

v, err := bm.Build()
v, err := bm.Build(tmpDir)
if err != nil {
return describeErr(tmpDir, "failed to build bundle", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/timoni/bundle_vet.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func runBundleVetCmd(cmd *cobra.Command, args []string) error {
return describeErr(workspace, "failed to parse bundle", err)
}

v, err := bm.Build()
v, err := bm.Build(workspace)
if err != nil {
return describeErr(workspace, "failed to build bundle", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/timoni/runtime_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func buildRuntime(files []string) (*apiv1.Runtime, error) {
return nil, describeErr(tmpDir, "failed to init runtime", err)
}

v, err := rb.Build()
v, err := rb.Build(tmpDir)
if err != nil {
return nil, describeErr(tmpDir, "failed to parse runtime", err)
}
Expand Down
10 changes: 6 additions & 4 deletions internal/engine/bundle_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
type BundleBuilder struct {
ctx *cue.Context
files []string
workspacesFiles map[string][]string
mapSourceToOrigin map[string]string
injector *RuntimeInjector
}
Expand All @@ -63,6 +64,7 @@ func NewBundleBuilder(ctx *cue.Context, files []string) *BundleBuilder {
b := &BundleBuilder{
ctx: ctx,
files: files,
workspacesFiles: make(map[string][]string),
mapSourceToOrigin: make(map[string]string, len(files)),
injector: NewRuntimeInjector(ctx),
}
Expand Down Expand Up @@ -116,26 +118,26 @@ func (b *BundleBuilder) InitWorkspace(workspace string, runtimeValues map[string
files = append(files, dstFile)
}

schemaFile := filepath.Join(workspace, fmt.Sprintf("%v.schema.cue", len(b.files)+1))
schemaFile := filepath.Join(workspace, fmt.Sprintf("%v.schema.cue", len(b.workspacesFiles[workspace])+1))
files = append(files, schemaFile)
if err := os.WriteFile(schemaFile, []byte(apiv1.BundleSchema), os.ModePerm); err != nil {
return err
}

b.files = files
b.workspacesFiles[workspace] = files
return nil
}

// Build builds a CUE instance for the specified files and returns the CUE value.
// A workspace must be initialised with InitWorkspace before calling this function.
func (b *BundleBuilder) Build() (cue.Value, error) {
func (b *BundleBuilder) Build(workspace string) (cue.Value, error) {
var value cue.Value
cfg := &load.Config{
Package: "_",
DataFiles: true,
}

ix := load.Instances(b.files, cfg)
ix := load.Instances(b.workspacesFiles[workspace], cfg)
if len(ix) == 0 {
return value, fmt.Errorf("no instances found")
}
Expand Down
18 changes: 10 additions & 8 deletions internal/engine/runtime_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ import (

// RuntimeBuilder compiles CUE definitions to Go Runtime objects.
type RuntimeBuilder struct {
ctx *cue.Context
files []string
ctx *cue.Context
files []string
workspacesFiles map[string][]string
}

// NewRuntimeBuilder creates a RuntimeBuilder for the given module and package.
Expand All @@ -45,8 +46,9 @@ func NewRuntimeBuilder(ctx *cue.Context, files []string) *RuntimeBuilder {
ctx = cuecontext.New()
}
b := &RuntimeBuilder{
ctx: ctx,
files: files,
ctx: ctx,
files: files,
workspacesFiles: make(map[string][]string),
}
return b
}
Expand Down Expand Up @@ -96,26 +98,26 @@ func (b *RuntimeBuilder) InitWorkspace(workspace string) error {
files = append(files, dstFile)
}

schemaFile := filepath.Join(workspace, fmt.Sprintf("%v.schema.cue", len(b.files)+1))
schemaFile := filepath.Join(workspace, fmt.Sprintf("%v.schema.cue", len(b.workspacesFiles[workspace])+1))
files = append(files, schemaFile)
if err := os.WriteFile(schemaFile, []byte(apiv1.RuntimeSchema), os.ModePerm); err != nil {
return err
}

b.files = files
b.workspacesFiles[workspace] = files
return nil
}

// Build builds a CUE instance for the specified files and returns the CUE value.
// A workspace must be initialised with InitWorkspace before calling this function.
func (b *RuntimeBuilder) Build() (cue.Value, error) {
func (b *RuntimeBuilder) Build(workspace string) (cue.Value, error) {
var value cue.Value
cfg := &load.Config{
Package: "_",
DataFiles: true,
}

ix := load.Instances(b.files, cfg)
ix := load.Instances(b.workspacesFiles[workspace], cfg)
if len(ix) == 0 {
return value, fmt.Errorf("no instances found")
}
Expand Down