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

Add Helm Test to cover additional attributes introduced in values.yaml used during kanister operator installation #2881

Merged
merged 38 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7709520
Add support for --dry-run in Helm Client
r4rajat Apr 16, 2024
8b0b5a8
refactor app installs to set dryRun as false
r4rajat Apr 17, 2024
40c7053
Add support for send output of helm install with dryRun enabled
r4rajat Apr 17, 2024
1d2dcc1
Add Helper Functions for Helm Installation Output
r4rajat May 2, 2024
4d196e0
Merge branch 'master' into add-supprt-for-dry-run-helm-client
r4rajat May 2, 2024
c5f8228
Address review comments
mabhi May 9, 2024
9b30899
Address lint error
mabhi May 9, 2024
672fcac
Initial commit
mabhi May 10, 2024
bef7b7a
Fixed test cases failures
mabhi May 10, 2024
7fb14d5
Address review comments
mabhi May 10, 2024
2b11b89
Merge branch 'add-supprt-for-dry-run-helm-client' into add-helm-test-…
mabhi May 10, 2024
4fe93c4
Address review comments
mabhi May 10, 2024
5753a0d
Added [nit]
mabhi May 10, 2024
eba1990
Merge branch 'add-supprt-for-dry-run-helm-client' into add-helm-test-…
mabhi May 11, 2024
594202a
Address comments
mabhi May 11, 2024
3876341
Address review comments
mabhi May 16, 2024
5820262
Merge branch 'add-supprt-for-dry-run-helm-client' into add-helm-test-…
mabhi May 16, 2024
00b786f
Removed un-used func
mabhi May 16, 2024
115b8dd
Fix lint error and helm test error
mabhi May 16, 2024
7473455
Merge branch 'add-supprt-for-dry-run-helm-client' into add-helm-test-…
mabhi May 16, 2024
0ac108c
Addressed review comments. Refactor func to include filter
mabhi May 16, 2024
ab760d9
Address refactoring review comments
mabhi May 16, 2024
dd907d7
Merge branch 'add-supprt-for-dry-run-helm-client' into add-helm-test-…
mabhi May 16, 2024
29a0a24
Address review comments
mabhi May 16, 2024
68759c9
Updated doc comments and removed unnecessary comments
mabhi May 17, 2024
41381b2
Merge branch 'add-supprt-for-dry-run-helm-client' into add-helm-test-…
mabhi May 17, 2024
d8643b9
Merge branch 'master' into add-supprt-for-dry-run-helm-client
mabhi May 17, 2024
5a2f53c
Minor refactor
mabhi May 17, 2024
9dd2f2a
Merge branch 'add-supprt-for-dry-run-helm-client' into add-helm-test-…
mabhi May 17, 2024
aebe4e6
Address review comments
mabhi May 17, 2024
81e29ad
Updated test comparison
mabhi May 17, 2024
dff1314
Shortened func comments
mabhi May 20, 2024
f6e5d1c
Addressed review comments
mabhi May 20, 2024
09af8e7
Merge branch 'add-supprt-for-dry-run-helm-client' into add-helm-test-…
mabhi May 20, 2024
ababf2c
Update helm helper func name and usages
mabhi May 20, 2024
afe354a
Addressed review comment
mabhi May 20, 2024
8831a70
Merge branch 'add-supprt-for-dry-run-helm-client' into add-helm-test-…
mabhi May 20, 2024
3051fac
Merge branch 'master' into add-helm-test-support-pr-2791
mabhi May 20, 2024
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 pkg/app/cassandra.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (cas *CassandraInstance) Install(ctx context.Context, namespace string) err
if err != nil {
return err
}
err = cli.Install(ctx, fmt.Sprintf("%s/%s", cas.chart.RepoName, cas.chart.Chart), cas.chart.Version, cas.chart.Release, cas.namespace, cas.chart.Values, true)
_, err = cli.Install(ctx, fmt.Sprintf("%s/%s", cas.chart.RepoName, cas.chart.Chart), cas.chart.Version, cas.chart.Release, cas.namespace, cas.chart.Values, true, false)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/cockroachdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (c *CockroachDB) Install(ctx context.Context, namespace string) error { //n
return errors.Wrapf(err, "Failed to install helm repo. app=%s repo=%s", c.name, c.chart.RepoName)
}

err = cli.Install(ctx, fmt.Sprintf("%s/%s", c.chart.RepoName, c.chart.Chart), c.chart.Version, c.chart.Release, c.namespace, c.chart.Values, false)
_, err = cli.Install(ctx, fmt.Sprintf("%s/%s", c.chart.RepoName, c.chart.Chart), c.chart.Version, c.chart.Release, c.namespace, c.chart.Values, false, false)
return errors.Wrapf(err, "Failed to install helm chart. app=%s chart=%s release=%s", c.name, c.chart.Chart, c.chart.Release)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/app/couchbase.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (cb *CouchbaseDB) Install(ctx context.Context, ns string) error { //nolint:
}

// Install cb operator, admission controller and cluster
err = cli.Install(ctx, fmt.Sprintf("%s/%s", cb.chart.RepoName, cb.chart.Chart), cb.chart.Version, cb.chart.Release, cb.namespace, cb.chart.Values, true)
_, err = cli.Install(ctx, fmt.Sprintf("%s/%s", cb.chart.RepoName, cb.chart.Chart), cb.chart.Version, cb.chart.Release, cb.namespace, cb.chart.Values, true, false)
return errors.Wrapf(err, "Failed to install helm chart. app=%s chart=%s release=%s", cb.name, cb.chart.Chart, cb.chart.Release)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/app/elasticsearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (esi *ElasticsearchInstance) Install(ctx context.Context, namespace string)
return err
}

err = cli.Install(ctx, fmt.Sprintf("%s/%s", esi.chart.RepoName, esi.chart.Chart), esi.chart.Version, esi.chart.Release, esi.namespace, esi.chart.Values, true)
_, err = cli.Install(ctx, fmt.Sprintf("%s/%s", esi.chart.RepoName, esi.chart.Chart), esi.chart.Version, esi.chart.Release, esi.namespace, esi.chart.Values, true, false)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/kafka.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (kc *KafkaCluster) Install(ctx context.Context, namespace string) error {
return errors.Wrapf(err, "Error adding helm repo for app %s.", kc.name)
}
log.Print("Installing kafka operator using helm.", field.M{"app": kc.name})
err = cli.Install(ctx, kc.chart.RepoName+"/"+kc.chart.Chart, kc.chart.Version, kc.chart.Release, kc.namespace, kc.chart.Values, true)
_, err = cli.Install(ctx, kc.chart.RepoName+"/"+kc.chart.Chart, kc.chart.Version, kc.chart.Release, kc.namespace, kc.chart.Values, true, false)
if err != nil {
return errors.Wrapf(err, "Error installing operator %s through helm.", kc.name)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/mariadb.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (m *MariaDB) Install(ctx context.Context, namespace string) error { //nolin
}

log.Print("Installing maria instance using helm.", field.M{"app": m.name})
err = cli.Install(ctx, m.chart.RepoName+"/"+m.chart.Chart, m.chart.Version, m.chart.Release, m.namespace, m.chart.Values, true)
_, err = cli.Install(ctx, m.chart.RepoName+"/"+m.chart.Chart, m.chart.Version, m.chart.Release, m.namespace, m.chart.Values, true, false)
if err != nil {
return errors.Wrapf(err, "Error intalling application %s through helm.", m.name)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/mongodb.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (mongo *MongoDB) Install(ctx context.Context, namespace string) error {
}

log.Print("Installing application using helm.", field.M{"app": mongo.name})
err = cli.Install(ctx, fmt.Sprintf("%s/%s", mongo.chart.RepoName, mongo.chart.Chart), mongo.chart.Version, mongo.chart.Release, mongo.namespace, mongo.chart.Values, true)
_, err = cli.Install(ctx, fmt.Sprintf("%s/%s", mongo.chart.RepoName, mongo.chart.Chart), mongo.chart.Version, mongo.chart.Release, mongo.namespace, mongo.chart.Values, true, false)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (mdb *MysqlDB) Install(ctx context.Context, namespace string) error { //nol
}

log.Print("Installing mysql instance using helm.", field.M{"app": mdb.name})
err = cli.Install(ctx, mdb.chart.RepoName+"/"+mdb.chart.Chart, mdb.chart.Version, mdb.chart.Release, mdb.namespace, mdb.chart.Values, true)
_, err = cli.Install(ctx, mdb.chart.RepoName+"/"+mdb.chart.Chart, mdb.chart.Version, mdb.chart.Release, mdb.namespace, mdb.chart.Values, true, false)
if err != nil {
return errors.Wrapf(err, "Error intalling application %s through helm.", mdb.name)
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/app/postgresql.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ func (pdb *PostgresDB) Install(ctx context.Context, ns string) error {
return err
}
// Install helm chart
return cli.Install(ctx, fmt.Sprintf("%s/%s", pdb.chart.RepoName, pdb.chart.Chart), pdb.chart.Version, pdb.chart.Release, pdb.namespace, pdb.chart.Values, true)
_, err = cli.Install(ctx, fmt.Sprintf("%s/%s", pdb.chart.RepoName, pdb.chart.Chart), pdb.chart.Version, pdb.chart.Release, pdb.namespace, pdb.chart.Values, true, false)
return err
}

func (pdb *PostgresDB) IsReady(ctx context.Context) (bool, error) {
Expand Down
34 changes: 27 additions & 7 deletions pkg/helm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,18 @@ func (h CliClient) UpdateRepo(ctx context.Context) error {
return nil
}

// Install installs helm chart with given release name
func (h CliClient) Install(ctx context.Context, chart, version, release, namespace string, values map[string]string, wait bool) error {
// Install installs a Helm chart in the specified namespace with the given release name and chart version.
// `wait` and `dryRun` can be set to `true` to make sure it adds `--wait` and `--dry-run` flags to the
// `helm install` command.
func (h CliClient) Install(
ctx context.Context,
chart,
version,
release,
namespace string,
values map[string]string,
wait,
dryRun bool) (string, error) {
log.Debug().Print("Installing helm chart", field.M{"chart": chart, "version": version, "release": release, "namespace": namespace})
var setVals string
for k, v := range values {
Expand All @@ -140,14 +150,24 @@ func (h CliClient) Install(ctx context.Context, chart, version, release, namespa
if wait {
cmd = append(cmd, "--wait")
}

if !dryRun {
out, err := RunCmdWithTimeout(ctx, h.helmBin, cmd)
if err != nil {
log.Error().Print("Error installing helm chart", field.M{"output": out})
return "", err
}
log.Debug().Print("Helm install output:", field.M{"output": out})
return out, nil
}
cmd = append(cmd, "--dry-run")
log.Debug().Print("Executing helm install command with dry-run enabled to capture rendered manifests:")
out, err := RunCmdWithTimeout(ctx, h.helmBin, cmd)
if err != nil {
log.Error().Print("Error installing helm chart", field.M{"output": out})
return err
log.Error().Print("Error installing chart with dry-run enabled", field.M{"output": out, "error": err})
return "", err
}
log.Debug().Print("Result", field.M{"output": out})
return nil
log.Debug().Print("Helm install dry-run enabled output:", field.M{"command": h.helmBin, "args": cmd, "output": out})
return out, nil
}

func (h CliClient) Upgrade(ctx context.Context, chart, version, release, namespace string, values map[string]string) error {
Expand Down
2 changes: 1 addition & 1 deletion pkg/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type Client interface {
RemoveRepo(ctx context.Context, name string) error
// Install installs helm chart with given release name in the namespace
// wait argument enables/disables --wait flag in 'helm install' command
Install(ctx context.Context, chart, version, release, namespace string, values map[string]string, wait bool) error
Install(ctx context.Context, chart, version, release, namespace string, values map[string]string, wait, dryRun bool) (string, error)
// Uninstall deletes helm release from the given namespace
Uninstall(ctx context.Context, release, namespace string) error
// Upgrade upgrades an installed helm release
Expand Down
87 changes: 87 additions & 0 deletions pkg/helm/helm_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2022 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package helm

import (
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/yaml"

"github.com/kanisterio/kanister/pkg/field"
"github.com/kanisterio/kanister/pkg/log"
)

type k8sObj struct {
ObjKind string `json:"kind"`
MetaData metav1.ObjectMeta `json:"metadata"`
}

type K8sObjectType string

const (
K8sObjectTypeDeployment K8sObjectType = "deployment"
)

type RenderedResource struct {
name string
// renderedManifest holds the dry run raw yaml of the resource.
renderedManifest string
}

type ResourceFilter func(kind K8sObjectType) bool

// ResourcesFromRenderedManifest extracts optionally filtered raw resource yamls from a given rendered manifest.
func ResourcesFromRenderedManifest(manifest string, filter ResourceFilter) []RenderedResource {
var ret []RenderedResource
// Get rid of the notes section, shown at the very end of the output.
manifestSections := strings.Split(manifest, "NOTES:")
// The actual rendered manifests start after first occurrence of `---`.
// Before this we have chart details, for example Name, Last Deployed, Status etc.
renderedResourcesYaml := strings.Split(manifestSections[0], "---")
for _, resourceYaml := range renderedResourcesYaml[1:] {
obj := k8sObj{}
if err := yaml.Unmarshal([]byte(resourceYaml), &obj); err != nil {
log.Error().Print("Failed to unmarshal k8s obj", field.M{"Error": err})
continue
}
k8sType := K8sObjectType(strings.ToLower(obj.ObjKind))
// Either append all rendered resource or filter.
if filter == nil || filter(k8sType) {
ret = append(ret, RenderedResource{
name: obj.MetaData.Name,
renderedManifest: resourceYaml,
})
}
}
return ret
}

// K8sObjectsFromRenderedResources unmarshals a list of rendered Kubernetes manifests
// into a map of Kubernetes objects name and object itself.
func K8sObjectsFromRenderedResources[T runtime.Object](resources []RenderedResource) (map[string]T, error) {
var nameAndObj = make(map[string]T)
var err error
for _, resource := range resources {
var obj T
if err = yaml.Unmarshal([]byte(resource.renderedManifest), &obj); err != nil {
log.Error().Print("Failed to unmarshal rendered resource yaml to K8s obj", field.M{"Error": err})
return nil, err
}
nameAndObj[resource.name] = obj
}
return nameAndObj, nil
}
4 changes: 2 additions & 2 deletions pkg/testing/helm/helm_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ func (h *HelmApp) AddRepo(name, url string) error {
return h.client.AddRepo(context.Background(), name, url)
}

func (h *HelmApp) Install() error {
func (h *HelmApp) Install() (string, error) {
ctx := context.Background()
return h.client.Install(ctx, h.chart, "", h.name, h.namespace, h.helmValues, true)
return h.client.Install(ctx, h.chart, "", h.name, h.namespace, h.helmValues, true, h.dryRun)
}

func (h *HelmApp) Upgrade(chart string, updatedValues map[string]string) error {
Expand Down
111 changes: 109 additions & 2 deletions pkg/testing/helm/helm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ import (
"testing"

. "gopkg.in/check.v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"

"github.com/kanisterio/kanister/pkg/helm"
"github.com/kanisterio/kanister/pkg/kube"
)

Expand Down Expand Up @@ -72,9 +75,9 @@ func (h *HelmTestSuite) TestUpgrade(c *C) {

// install released version of kanister
c.Log("Installing kanister release")
err := h.helmApp.Install()
// TODO: Use manifests to test the helm charts
_, err := h.helmApp.Install()
c.Assert(err, IsNil)

// wait for kanister deployment to be ready
err = kube.WaitOnDeploymentReady(ctx, h.kubeClient, h.helmApp.namespace, h.deploymentName)
c.Assert(err, IsNil)
Expand All @@ -92,6 +95,110 @@ func (h *HelmTestSuite) TestUpgrade(c *C) {
c.Assert(err, IsNil)
}

func (h *HelmTestSuite) TestResourcesFromManifestAfterDryRunHelmInstall(c *C) {
defer func() {
h.helmApp.dryRun = false
}()
c.Log("Installing kanister release - Dry run")
h.helmApp.dryRun = true
out, err := h.helmApp.Install()
c.Assert(err, IsNil)
// Fetch all resources
resources := helm.ResourcesFromRenderedManifest(out, nil)
c.Assert(len(resources) > 0, Equals, true)
}

// TestSelectedDeploymentAttrFromKanisterHelmDryRunInstall test case does a dry run install of the `kanister` helm chart and validates
// use cases for `nodeSelector` and `toleration` attributes in the helmValues.yaml. This function is specific to `deployment` resource.
func (h *HelmTestSuite) TestSelectedDeploymentAttrFromKanisterHelmDryRunInstall(c *C) {
nodeSelector := map[string]string{
"selector-key": "selector-value",
}
toleration := []corev1.Toleration{
{
Key: "taint-key",
Operator: corev1.TolerationOpEqual,
Value: "taint-value",
Effect: corev1.TaintEffectNoSchedule,
},
}

var testCases = []struct {
testName string
helmValues map[string]string
expectedNodeSelector map[string]string
expectedTolerations []corev1.Toleration
}{
{
testName: "Both nodeSelector and tolerations are present",
helmValues: map[string]string{
"bpValidatingWebhook.enabled": "false",
"nodeSelector.selector-key": "selector-value",
"tolerations[0].key": "taint-key",
"tolerations[0].operator": "Equal",
"tolerations[0].value": "taint-value",
"tolerations[0].effect": "NoSchedule",
},
expectedNodeSelector: nodeSelector,
expectedTolerations: toleration,
},
{
testName: "Only nodeSelector is present",
helmValues: map[string]string{
"bpValidatingWebhook.enabled": "false",
"nodeSelector.selector-key": "selector-value",
},
expectedNodeSelector: nodeSelector,
expectedTolerations: nil,
},
{
testName: "Only tolerations is present",
helmValues: map[string]string{
"bpValidatingWebhook.enabled": "false",
"tolerations[0].key": "taint-key",
"tolerations[0].operator": "Equal",
"tolerations[0].value": "taint-value",
"tolerations[0].effect": "NoSchedule",
},
expectedNodeSelector: nil,
expectedTolerations: toleration,
},
{
testName: "Both nodeSelector and tolerations are not present",
helmValues: map[string]string{
"bpValidatingWebhook.enabled": "false",
},
expectedNodeSelector: nil,
expectedTolerations: nil,
},
}
for _, tc := range testCases {
c.Logf("Test name:%s ", tc.testName)
defer func() {
h.helmApp.dryRun = false
}()
// Installing kanister release from local kanister-operator - Dry run"
testApp, err := NewHelmApp(tc.helmValues, kanisterName, "../../../helm/kanister-operator", kanisterName, "", true)
c.Assert(err, IsNil)

out, err := testApp.Install()
c.Assert(err, IsNil)
resources := helm.ResourcesFromRenderedManifest(out, func(kind helm.K8sObjectType) bool {
return kind == helm.K8sObjectTypeDeployment
})
c.Assert(len(resources) > 0, Equals, true)
// Take the deployment resources
deployments, err := helm.K8sObjectsFromRenderedResources[*appsv1.Deployment](resources)
c.Assert(err, IsNil)
// Use only the required deployment
var obj = deployments[h.deploymentName]
c.Assert(obj, NotNil)

c.Assert(obj.Spec.Template.Spec.NodeSelector, DeepEquals, tc.expectedNodeSelector)
c.Assert(obj.Spec.Template.Spec.Tolerations, DeepEquals, tc.expectedTolerations)
}
}

func (h *HelmTestSuite) TearDownSuite(c *C) {
c.Log("Uninstalling chart")
err := h.helmApp.Uninstall()
Expand Down
Loading