Skip to content

Commit

Permalink
Add support for --dry-run in Helm Client (#2824)
Browse files Browse the repository at this point in the history
* Add support for --dry-run in Helm Client

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* refactor app installs to set dryRun as false

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Add support for send output of helm install with dryRun enabled

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Add Helper Functions for Helm Installation Output

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>

* Address review comments

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Address lint error

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Address review comments

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Address review comments

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Added [nit]

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Address review comments

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Removed un-used func

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Fix lint error and helm test error

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Address refactoring review comments

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Updated doc comments and removed unnecessary comments

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Minor refactor

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Shortened func comments

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Addressed review comments

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

* Addressed review comment

Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>

---------

Signed-off-by: Rajat Gupta <rajat.gupta@veeam.com>
Signed-off-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>
Co-authored-by: Abhijit Mukherjee <abhijit.mukherjee@infracloud.io>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed May 20, 2024
1 parent 28d0947 commit 9556d54
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 21 deletions.
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
83 changes: 83 additions & 0 deletions pkg/helm/helm_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// 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

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
18 changes: 16 additions & 2 deletions pkg/testing/helm/helm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
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 +73,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 +93,19 @@ 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)
}

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

0 comments on commit 9556d54

Please sign in to comment.