From 3c3d8392320b2cdeba70ae79ff96d0d2a4410735 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Wed, 29 Jan 2025 17:35:17 -0800 Subject: [PATCH] refactor: save module bundles instead of individual modules --- cli/cmd/cmds/module/template.go | 4 +- lib/project/deployment/deployer/deployer.go | 27 ++-- .../deployment/deployer/deployer_test.go | 62 +++++---- lib/project/deployment/generator/generator.go | 32 ++--- .../deployment/generator/generator_test.go | 124 ++++++++++++++---- lib/project/deployment/module.go | 17 +++ 6 files changed, 182 insertions(+), 84 deletions(-) diff --git a/cli/cmd/cmds/module/template.go b/cli/cmd/cmds/module/template.go index ba4759d8..371ac3d5 100644 --- a/cli/cmd/cmds/module/template.go +++ b/cli/cmd/cmds/module/template.go @@ -25,8 +25,8 @@ func (c *TemplateCmd) Run(ctx run.RunContext) error { } var out string - for _, module := range result { - out += fmt.Sprintf("%s---\n", module.Manifests) + for _, manifest := range result.Manifests { + out += fmt.Sprintf("%s---\n", manifest) } fmt.Print(strings.TrimSuffix(out, "---\n")) diff --git a/lib/project/deployment/deployer/deployer.go b/lib/project/deployment/deployer/deployer.go index 1013287b..d7ca0cde 100644 --- a/lib/project/deployment/deployer/deployer.go +++ b/lib/project/deployment/deployer/deployer.go @@ -59,25 +59,26 @@ func (d *Deployer) Deploy() error { return fmt.Errorf("could not generate deployment manifests: %w", err) } - for name, result := range result { + modPath := filepath.Join(prjPath, "mod.cue") + d.logger.Info("Writing module", "path", modPath) + if err := r.WriteFile(modPath, []byte(result.Module)); err != nil { + return fmt.Errorf("could not write module: %w", err) + } + + if err := r.StageFile(modPath); err != nil { + return fmt.Errorf("could not add module to working tree: %w", err) + } + + for name, result := range result.Manifests { manPath := filepath.Join(prjPath, fmt.Sprintf("%s.yaml", name)) - modPath := filepath.Join(prjPath, fmt.Sprintf("%s.mod.cue", name)) d.logger.Info("Writing manifest", "path", manPath) - if err := r.WriteFile(manPath, []byte(result.Manifests)); err != nil { + if err := r.WriteFile(manPath, []byte(result)); err != nil { return fmt.Errorf("could not write manifest: %w", err) } if err := r.StageFile(manPath); err != nil { return fmt.Errorf("could not add manifest to working tree: %w", err) } - - d.logger.Info("Writing module", "path", modPath) - if err := r.WriteFile(modPath, []byte(result.Module)); err != nil { - return fmt.Errorf("could not write values: %w", err) - } - if err := r.StageFile(modPath); err != nil { - return fmt.Errorf("could not add values to working tree: %w", err) - } } if !d.dryrun { @@ -101,8 +102,8 @@ func (d *Deployer) Deploy() error { } else { d.logger.Info("Dry-run: not committing or pushing changes") d.logger.Info("Dumping manifests") - for _, r := range result { - fmt.Println(string(r.Manifests)) + for _, r := range result.Manifests { + fmt.Println(string(r)) } } diff --git a/lib/project/deployment/deployer/deployer_test.go b/lib/project/deployment/deployer/deployer_test.go index 2c23587b..1537218d 100644 --- a/lib/project/deployment/deployer/deployer_test.go +++ b/lib/project/deployment/deployer/deployer_test.go @@ -26,14 +26,12 @@ import ( ) func TestDeployerDeploy(t *testing.T) { - newProject := func(name string, module schema.DeploymentModule) project.Project { + newProject := func(name string, bundle schema.DeploymentModuleBundle) project.Project { return project.Project{ Blueprint: schema.Blueprint{ Project: schema.Project{ Deployment: schema.Deployment{ - Modules: map[string]schema.DeploymentModule{ - "main": module, - }, + Modules: bundle, }, }, Global: schema.Global{ @@ -80,13 +78,15 @@ func TestDeployerDeploy(t *testing.T) { name: "success", project: newProject( "project", - schema.DeploymentModule{ - Instance: "instance", - Name: "module", - Namespace: "default", - Registry: "registry", - Values: map[string]string{"key": "value"}, - Version: "v1.0.0", + schema.DeploymentModuleBundle{ + "main": { + Instance: "instance", + Name: "module", + Namespace: "default", + Registry: "registry", + Values: map[string]string{"key": "value"}, + Version: "v1.0.0", + }, }, ), files: nil, @@ -98,7 +98,7 @@ func TestDeployerDeploy(t *testing.T) { require.NoError(t, err) assert.True(t, e) - e, err = afero.Exists(r.fs, "/repo/root/test/apps/project/main.mod.cue") + e, err = afero.Exists(r.fs, "/repo/root/test/apps/project/mod.cue") require.NoError(t, err) assert.True(t, e) @@ -107,16 +107,18 @@ func TestDeployerDeploy(t *testing.T) { assert.Equal(t, "manifest", string(c)) mod := `{ - instance: "instance" - name: "module" - namespace: "default" - registry: "registry" - values: { - key: "value" + main: { + instance: "instance" + name: "module" + namespace: "default" + registry: "registry" + values: { + key: "value" + } + version: "v1.0.0" } - version: "v1.0.0" }` - c, err = afero.ReadFile(r.fs, "/repo/root/test/apps/project/main.mod.cue") + c, err = afero.ReadFile(r.fs, "/repo/root/test/apps/project/mod.cue") require.NoError(t, err) assert.Equal(t, mod, string(c)) @@ -139,13 +141,15 @@ func TestDeployerDeploy(t *testing.T) { name: "dry run with extra files", project: newProject( "project", - schema.DeploymentModule{ - Instance: "instance", - Name: "module", - Namespace: "default", - Registry: "registry", - Values: map[string]string{"key": "value"}, - Version: "v1.0.0", + schema.DeploymentModuleBundle{ + "main": { + Instance: "instance", + Name: "module", + Namespace: "default", + Registry: "registry", + Values: map[string]string{"key": "value"}, + Version: "v1.0.0", + }, }, ), files: map[string]string{ @@ -159,7 +163,7 @@ func TestDeployerDeploy(t *testing.T) { require.NoError(t, err) assert.True(t, e) - e, err = afero.Exists(r.fs, "/repo/root/test/apps/project/main.mod.cue") + e, err = afero.Exists(r.fs, "/repo/root/test/apps/project/mod.cue") require.NoError(t, err) assert.True(t, e) @@ -177,7 +181,7 @@ func TestDeployerDeploy(t *testing.T) { fst = st.File("root/test/apps/project/main.yaml") assert.Equal(t, fst.Staging, gg.Added) - fst = st.File("root/test/apps/project/main.mod.cue") + fst = st.File("root/test/apps/project/mod.cue") assert.Equal(t, fst.Staging, gg.Added) head, err := r.repo.Head() diff --git a/lib/project/deployment/generator/generator.go b/lib/project/deployment/generator/generator.go index d8a39d23..4b095d8b 100644 --- a/lib/project/deployment/generator/generator.go +++ b/lib/project/deployment/generator/generator.go @@ -11,7 +11,7 @@ import ( // GeneratorResult is the result of a deployment generation. type GeneratorResult struct { - Manifests []byte + Manifests map[string][]byte Module []byte } @@ -22,37 +22,37 @@ type Generator struct { } // GenerateBundle generates manifests for a deployment bundle. -func (d *Generator) GenerateBundle(b schema.DeploymentModuleBundle) (map[string]GeneratorResult, error) { - results := make(map[string]GeneratorResult) +func (d *Generator) GenerateBundle(b schema.DeploymentModuleBundle) (GeneratorResult, error) { + bundle, err := deployment.DumpBundle(b) + if err != nil { + return GeneratorResult{}, fmt.Errorf("failed to dump bundle: %w", err) + } + + results := make(map[string][]byte) for name, module := range b { d.logger.Debug("Generating module", "name", name) result, err := d.Generate(module) if err != nil { - return nil, fmt.Errorf("failed to generate module %s: %w", name, err) + return GeneratorResult{}, fmt.Errorf("failed to generate module %s: %w", name, err) } results[name] = result } - return results, nil + return GeneratorResult{ + Manifests: results, + Module: bundle, + }, nil } // Generate generates manifests for a deployment module. -func (d *Generator) Generate(m schema.DeploymentModule) (GeneratorResult, error) { +func (d *Generator) Generate(m schema.DeploymentModule) ([]byte, error) { manifests, err := d.mg.Generate(m) if err != nil { - return GeneratorResult{}, fmt.Errorf("failed to generate manifest for module: %w", err) + return nil, fmt.Errorf("failed to generate manifest for module: %w", err) } - module, err := deployment.DumpModule(m) - if err != nil { - return GeneratorResult{}, fmt.Errorf("failed to dump module: %w", err) - } - - return GeneratorResult{ - Manifests: manifests, - Module: module, - }, nil + return manifests, nil } // NewGenerator creates a new deployment generator. diff --git a/lib/project/deployment/generator/generator_test.go b/lib/project/deployment/generator/generator_test.go index 39031646..1e0b19f3 100644 --- a/lib/project/deployment/generator/generator_test.go +++ b/lib/project/deployment/generator/generator_test.go @@ -12,51 +12,59 @@ import ( "github.com/stretchr/testify/require" ) -func TestGeneratorGenerate(t *testing.T) { +func TestGeneratorGenerateBundle(t *testing.T) { ctx := cuecontext.New() tests := []struct { name string - module schema.DeploymentModule + bundle schema.DeploymentModuleBundle yaml string err bool validate func(t *testing.T, result GeneratorResult, err error) }{ { name: "full", - module: schema.DeploymentModule{ - Instance: "instance", - Name: "test", - Namespace: "default", - Registry: "registry", - Values: ctx.CompileString(`foo: "bar"`), - Version: "1.0.0", + bundle: schema.DeploymentModuleBundle{ + "test": schema.DeploymentModule{ + Instance: "instance", + Name: "test", + Namespace: "default", + Registry: "registry", + Values: ctx.CompileString(`foo: "bar"`), + Version: "1.0.0", + }, }, yaml: "test", err: false, validate: func(t *testing.T, result GeneratorResult, err error) { require.NoError(t, err) - assert.Equal(t, "test", string(result.Manifests)) m := `{ - instance: "instance" - name: "test" - namespace: "default" - registry: "registry" - values: { - foo: "bar" + test: { + instance: "instance" + name: "test" + namespace: "default" + registry: "registry" + values: { + foo: "bar" + } + version: "1.0.0" } - version: "1.0.0" }` assert.Equal(t, m, string(result.Module)) + assert.Equal(t, "test", string(result.Manifests["test"])) }, }, { name: "manifest error", - module: schema.DeploymentModule{ - Name: "test", - Namespace: "default", - Values: ctx.CompileString(`foo: "bar"`), - Version: "1.0.0", + bundle: schema.DeploymentModuleBundle{ + "test": schema.DeploymentModule{ + Instance: "instance", + Name: "test", + Namespace: "default", + Registry: "registry", + Values: ctx.CompileString(`foo: "bar"`), + Version: "1.0.0", + }, }, yaml: "test", err: true, @@ -66,15 +74,83 @@ func TestGeneratorGenerate(t *testing.T) { }, { name: "module error", + bundle: schema.DeploymentModuleBundle{ + "test": schema.DeploymentModule{ + Instance: "instance", + Name: "test", + Namespace: "default", + Registry: "registry", + Values: fmt.Errorf("error"), + Version: "1.0.0", + }, + }, + yaml: "test", + err: false, + validate: func(t *testing.T, result GeneratorResult, err error) { + assert.Error(t, err) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mg := &mocks.ManifestGeneratorMock{ + GenerateFunc: func(mod schema.DeploymentModule) ([]byte, error) { + if tt.err { + return nil, fmt.Errorf("error") + } + + return []byte(tt.yaml), nil + }, + } + gen := Generator{ + mg: mg, + logger: testutils.NewNoopLogger(), + } + + result, err := gen.GenerateBundle(tt.bundle) + tt.validate(t, result, err) + }) + } +} + +func TestGeneratorGenerate(t *testing.T) { + ctx := cuecontext.New() + tests := []struct { + name string + module schema.DeploymentModule + yaml string + err bool + validate func(t *testing.T, result []byte, err error) + }{ + { + name: "full", module: schema.DeploymentModule{ + Instance: "instance", Name: "test", Namespace: "default", - Values: fmt.Errorf("error"), + Registry: "registry", + Values: ctx.CompileString(`foo: "bar"`), Version: "1.0.0", }, yaml: "test", err: false, - validate: func(t *testing.T, result GeneratorResult, err error) { + validate: func(t *testing.T, result []byte, err error) { + require.NoError(t, err) + assert.Equal(t, "test", string(result)) + }, + }, + { + name: "manifest error", + module: schema.DeploymentModule{ + Name: "test", + Namespace: "default", + Values: ctx.CompileString(`foo: "bar"`), + Version: "1.0.0", + }, + yaml: "test", + err: true, + validate: func(t *testing.T, result []byte, err error) { assert.Error(t, err) }, }, diff --git a/lib/project/deployment/module.go b/lib/project/deployment/module.go index a955d94e..cefd4c21 100644 --- a/lib/project/deployment/module.go +++ b/lib/project/deployment/module.go @@ -24,3 +24,20 @@ func DumpModule(mod schema.DeploymentModule) ([]byte, error) { return src, nil } + +// DumpBundle dumps the deployment module bundle to CUE source. +func DumpBundle(mod schema.DeploymentModuleBundle) ([]byte, error) { + ctx := cuecontext.New() + v := ctx.Encode(mod) + + if v.Err() != nil { + return nil, fmt.Errorf("failed to encode bundle: %w", v.Err()) + } + + src, err := format.Node(v.Syntax()) + if err != nil { + return nil, fmt.Errorf("failed to format bundle: %w", err) + } + + return src, nil +}