Skip to content

Commit

Permalink
Add tests of multicluster install command (#7111)
Browse files Browse the repository at this point in the history
Currently there are no tests of the output of the multicluster install command
unlike the install and viz install commands. This makes it error prone to hard
to validate changes. This is motivated by the addition of an ha mode to the
multicluster components discussed in #7082.

This change adds two test cases and refactors the install command to look like
viz install making it easily testable. This means in practice that the body of
the command is moved into an install function. Here we extract external data,
eg. values, and delegates the values to a render function that handles the
actual rendering.

This is a non-functional change and the output used for the
install_default.golden file is based of the main branch to validate this.

Signed-off-by: Crevil <[email protected]>
(cherry picked from commit fddfb74)
Signed-off-by: Oliver Gould <[email protected]>
  • Loading branch information
Crevil authored and olix0r committed Apr 14, 2022
1 parent 3daec14 commit 1bc71f3
Show file tree
Hide file tree
Showing 5 changed files with 922 additions and 83 deletions.
173 changes: 90 additions & 83 deletions multicluster/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"errors"
"fmt"
"io"
"os"
"path"
"time"
Expand Down Expand Up @@ -68,89 +69,7 @@ A full list of configurable values can be found at https://github.com/linkerd/li
APIAddr: apiAddr,
RetryDeadline: time.Now().Add(wait),
})

values, err := buildMulticlusterInstallValues(cmd.Context(), options)

if err != nil {
return err
}

// Render raw values and create chart config
rawValues, err := yaml.Marshal(values)
if err != nil {
return err
}

files := []*chartloader.BufferedFile{
{Name: chartutil.ChartfileName},
{Name: "templates/namespace.yaml"},
{Name: "templates/gateway.yaml"},
{Name: "templates/proxy-admin-policy.yaml"},
{Name: "templates/gateway-policy.yaml"},
{Name: "templates/psp.yaml"},
{Name: "templates/remote-access-service-mirror-rbac.yaml"},
{Name: "templates/link-crd.yaml"},
{Name: "templates/service-mirror-policy.yaml"},
}

var partialFiles []*loader.BufferedFile
for _, template := range charts.L5dPartials {
partialFiles = append(partialFiles,
&loader.BufferedFile{Name: template},
)
}

// Load all multicluster install chart files into buffer
if err := charts.FilesReader(static.Templates, helmMulticlusterDefaultChartName+"/", files); err != nil {
return err
}

// Load all partial chart files into buffer
if err := charts.FilesReader(partials.Templates, "", partialFiles); err != nil {
return err
}

// Create a Chart obj from the files
chart, err := loader.LoadFiles(append(files, partialFiles...))
if err != nil {
return err
}

// Store final Values generated from values.yaml and CLI flags
err = yaml.Unmarshal(rawValues, &chart.Values)
if err != nil {
return err
}

// Create values override
valuesOverrides, err := valuesOptions.MergeValues(nil)
if err != nil {
return err
}

vals, err := chartutil.CoalesceValues(chart, valuesOverrides)
if err != nil {
return err
}

// Attach the final values into the `Values` field for rendering to work
renderedTemplates, err := engine.Render(chart, map[string]interface{}{"Values": vals})
if err != nil {
return err
}

// Merge templates and inject
var buf bytes.Buffer
for _, tmpl := range chart.Templates {
t := path.Join(chart.Metadata.Name, tmpl.Name)
if _, err := buf.WriteString(renderedTemplates[t]); err != nil {
return err
}
}
stdout.Write(buf.Bytes())
stdout.Write([]byte("---\n"))

return nil
return install(cmd.Context(), stdout, options, valuesOptions)
},
}

Expand Down Expand Up @@ -178,6 +97,94 @@ A full list of configurable values can be found at https://github.com/linkerd/li
return cmd
}

func install(ctx context.Context, w io.Writer, options *multiclusterInstallOptions, valuesOptions valuespkg.Options) error {
values, err := buildMulticlusterInstallValues(ctx, options)
if err != nil {
return err
}

// Create values override
valuesOverrides, err := valuesOptions.MergeValues(nil)
if err != nil {
return err
}

return render(w, values, valuesOverrides)
}

func render(w io.Writer, values *multicluster.Values, valuesOverrides map[string]interface{}) error {
files := []*chartloader.BufferedFile{
{Name: chartutil.ChartfileName},
{Name: chartutil.ValuesfileName},
{Name: "templates/namespace.yaml"},
{Name: "templates/gateway.yaml"},
{Name: "templates/proxy-admin-policy.yaml"},
{Name: "templates/gateway-policy.yaml"},
{Name: "templates/psp.yaml"},
{Name: "templates/remote-access-service-mirror-rbac.yaml"},
{Name: "templates/link-crd.yaml"},
{Name: "templates/service-mirror-policy.yaml"},
}

var partialFiles []*loader.BufferedFile
for _, template := range charts.L5dPartials {
partialFiles = append(partialFiles,
&loader.BufferedFile{Name: template},
)
}

// Load all multicluster install chart files into buffer
if err := charts.FilesReader(static.Templates, helmMulticlusterDefaultChartName+"/", files); err != nil {
return err
}

// Load all partial chart files into buffer
if err := charts.FilesReader(partials.Templates, "", partialFiles); err != nil {
return err
}

// Create a Chart obj from the files
chart, err := loader.LoadFiles(append(files, partialFiles...))
if err != nil {
return err
}

// Render raw values and create chart config
rawValues, err := yaml.Marshal(values)
if err != nil {
return err
}
// Store final Values generated from values.yaml and CLI flags
err = yaml.Unmarshal(rawValues, &chart.Values)
if err != nil {
return err
}

vals, err := chartutil.CoalesceValues(chart, valuesOverrides)
if err != nil {
return err
}

// Attach the final values into the `Values` field for rendering to work
renderedTemplates, err := engine.Render(chart, map[string]interface{}{"Values": vals})
if err != nil {
return err
}

// Merge templates and inject
var buf bytes.Buffer
for _, tmpl := range chart.Templates {
t := path.Join(chart.Metadata.Name, tmpl.Name)
if _, err := buf.WriteString(renderedTemplates[t]); err != nil {
return err
}
}
w.Write(buf.Bytes())
w.Write([]byte("---\n"))

return nil
}

func newMulticlusterInstallOptionsWithDefault() (*multiclusterInstallOptions, error) {
defaults, err := multicluster.NewInstallValues()
if err != nil {
Expand Down
46 changes: 46 additions & 0 deletions multicluster/cmd/install_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cmd

import (
"bytes"
"fmt"
"testing"

multicluster "github.com/linkerd/linkerd2/multicluster/values"
"github.com/linkerd/linkerd2/pkg/charts"
)

func TestRender(t *testing.T) {
// pin values that are changed by render functions on each test run
defaultValues := map[string]interface{}{}

testCases := []struct {
values map[string]interface{}
multiclusterValues *multicluster.Values
goldenFileName string
}{
{
nil,
nil,
"install_default.golden",
},
{
map[string]interface{}{
"enablePSP": "true",
},
nil,
"install_psp.golden",
},
}

for i, tc := range testCases {
tc := tc // pin
t.Run(fmt.Sprintf("%d: %s", i, tc.goldenFileName), func(t *testing.T) {
var buf bytes.Buffer
// Merge overrides with default
if err := render(&buf, tc.multiclusterValues, charts.MergeMaps(defaultValues, tc.values)); err != nil {
t.Fatalf("Failed to render templates: %v", err)
}
testDataDiffer.DiffTestdata(t, tc.goldenFileName, buf.String())
})
}
}
23 changes: 23 additions & 0 deletions multicluster/cmd/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package cmd

import (
"flag"
"os"
"testing"

"github.com/linkerd/linkerd2/testutil"
)

var (
testDataDiffer testutil.TestDataDiffer
)

// TestMain parses flags before running tests
func TestMain(m *testing.M) {
flag.BoolVar(&testDataDiffer.UpdateFixtures, "update", false, "update text fixtures in place")
prettyDiff := os.Getenv("LINKERD_TEST_PRETTY_DIFF") != ""
flag.BoolVar(&testDataDiffer.PrettyDiff, "pretty-diff", prettyDiff, "display the full text when diffing")
flag.StringVar(&testDataDiffer.RejectPath, "reject-path", "", "write results for failed tests to this path (path is relative to the test location)")
flag.Parse()
os.Exit(m.Run())
}
Loading

0 comments on commit 1bc71f3

Please sign in to comment.