1. Tutorials
  2. Install Helm Charts using the Chart resource

Install Helm Charts using the Chart resource

This is the second part of the tutorial on how to install Helm on Kubernetes using Pulumi.

In this tutorial, we will learn how to install a Helm chart on Kubernetes using the Pulumi Kubernetes provider and the Chart resource. In the first part, we used the Release resource to install a Helm chart on Kubernetes, here is the link to the first part: How to Install Helm Charts on Kubernetes with Pulumi - Part 1.

Most of the third-party applications that you want to install on your Kubernetes cluster, like whole monitoring stacks, databases, and other applications, are most likely available as Helm charts.

On services like Artifact Hub, you can find a lot of Helm charts that you can use to install applications on your Kubernetes cluster.

The Pulumi Kubernetes provider offers two different ways to install Helm on Kubernetes:

  • Using the Release resource
  • Using the Chart resource

The Helm Chart resource renders the templates from your chart and then manages the objects directly with the Pulumi Kubernetes provider. There was recently a new version of the Chart resource released, and in this blog post you will learn more about the improvements.

The Helm Release resource uses the Helm SDK to install the Helm chart on your Kubernetes cluster.

In this tutorial, you'll learn:

  • How to install Helm on Kubernetes using Pulumi

Prerequisites:

Why using the Chart resource over the Release resource?

The Helm Chart resource renders the templates from your chart and then manages the objects directly with the Pulumi Kubernetes provider. Chart is implemented as a Component Resource which provide a number of benefits for Pulumi users:

Benefits

  1. Visibility into all resources encapsulated by the Chart in Pulumi’s state, allowing users to directly query properties of individual resources.
  2. Tight integration with Pulumi’s Policy-as-Code framework - CrossGuard to enforce policies on all resources installed by Helm charts
  3. Ability to leverage transformations to programmatically manipulate resources installed by Helm charts in any of the Pulumi supported programming languages
  4. Detailed previews and diffs rendered in the Pulumi CLI and Console for each Kubernetes resource resulting from Helm Chart config changes

We have seen significant adoption of Chart over the years. However, since these resources are not directly managed by Helm, the following limitations apply:

Limitations

  1. No support for Helm Chart Hooks - i.e. equivalent of running helm install with the --no-hooks option
  2. No ability to import existing Helm releases into Pulumi state
  3. No interoperability using the Helm CLI on resources installed by Pulumi

Deploying a Helm chart with Chart resource

To start, login to the Pulumi CLI and create a new Pulumi project. You can use the following command to create a new Pulumi project and select from the list of templates the kubernetes-<your-programming-language> template.

# Choose your favorite Pulumi supported language
pulumi new kubernetes-<your-programming-language>

This will create a new Pulumi project with the necessary files to deploy Kubernetes resources and some example resources. Copy the following code snippet into your index.ts, index.py, index.go, Program.cs, or Pulumi.yaml file.

import * as pulumi from "@pulumi/pulumi";
import * as kubernetes from "@pulumi/kubernetes";

const applyPatchForceAnnotation = async (args: pulumi.ResourceTransformArgs) => {
    switch (args.type) {
        case "kubernetes:helm.sh/v4:Chart":
            break;
        default:
            args.props.metadata.annotations = {
                "cost-center": "12345",
                ...args.props.metadata.annotations,
            };
    }
    return {
        props: args.props,
        opts: args.opts,
    };
};

const ns = new kubernetes.core.v1.Namespace("cert-manager", {
    metadata: {
        name: "cert-manager",
    },
});

const certman = new kubernetes.helm.v4.Chart(
    "cert-manager",
    {
        namespace: "cert-manager",
        chart: "oci://registry-1.docker.io/bitnamicharts/cert-manager",
        version: "1.3.1",
    },
    { transforms: [applyPatchForceAnnotation] },
);
import pulumi
import pulumi_kubernetes as kubernetes

def apply_patchforce_annotation(args: pulumi.ResourceTransformArgs):
    if not args.type_ == "kubernetes:helm.sh/v4:Chart":
        if not "metadata" in args.props:
            args.props["metadata"] = {}
        if not "annotations" in args.props["metadata"]:
            args.props["metadata"]["annotations"] = {}
        args.props["metadata"]["annotations"]["cost-center"] = "12345"

    return pulumi.ResourceTransformResult(props=args.props, opts=args.opts)


certman = kubernetes.helm.v4.Chart(
    "cert-manager",
    namespace="cert-manager",
    chart="oci://registry-1.docker.io/bitnamicharts/cert-manager",
    version="1.3.1",
    opts=pulumi.ResourceOptions(transforms=[apply_patchforce_annotation])
)
package main

import (
	"context"

	helmv4 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/helm/v4"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi/internals"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func main() {

	applyPatchForceAnnotation := func(ctx context.Context, rta *pulumi.XResourceTransformArgs) *pulumi.XResourceTransformResult {
		transform := func(applier interface{}) {
			o := rta.Props.ToMapOutputWithContext(ctx).ApplyT(applier)
			r, err := internals.UnsafeAwaitOutput(ctx, o)
			if err != nil {
				panic(err)
			}
			rta.Props = r.Value.(pulumi.Map)
		}

		switch rta.Type {
		case "kubernetes:helm.sh/v4:Chart":
			// Do nothing for Helm charts
		default:
			transform(func(obj map[string]any) pulumi.Map {
				// note: obj is an ordinary Unstructured object at this point.
				err := unstructured.SetNestedField(obj, "12345", "metadata", "annotations", "cost-center")
				if err != nil {
					return nil
				}
				return pulumi.ToMap(obj)
			})
		}
		return &pulumi.XResourceTransformResult{
			Props: rta.Props,
			Opts:  rta.Opts,
		}
	}

	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := helmv4.NewChart(ctx, "cert-manager", &helmv4.ChartArgs{
			Namespace: pulumi.String("cert-manager"),
			Chart:     pulumi.String("oci://registry-1.docker.io/bitnamicharts/cert-manager"),
			Version:   pulumi.String("1.3.1"),
		}, pulumi.Transforms([]pulumi.XResourceTransform{applyPatchForceAnnotation}))
		if err != nil {
			return err
		}
		return nil
	})
}
using Pulumi;
using Pulumi.Experimental.Provider;
using Pulumi.Kubernetes.Helm.V4;
using Pulumi.Kubernetes.Types.Inputs;
using Pulumi.Kubernetes.Types.Inputs.Helm.V4;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Immutable;

await Deployment.RunAsync<MyStack>();


class MyStack : Stack
{
    public MyStack() 
    {
        var certman = new Chart("cert-manager", new ChartArgs
        {
            Namespace = "cert-manager",
            Chart = "oci://registry-1.docker.io/bitnamicharts/cert-manager",
            Version = "1.3.1",
        }, 
        new ComponentResourceOptions
        {
            ResourceTransforms =
            {
               async (args, _) => 
                {
                    Console.WriteLine("Transforming resource");
                    if (args.Type != "kubernetes:helm.sh/v4:Chart")
                    {
                    var myTags = ImmutableDictionary.Create<string, object>().Add("cost-center", "12345");

                    InputMap<object> tags =
                    args.Args.TryGetValue("metadata", out var tagsValue) && tagsValue is not null ?
                        tagsValue is Output<ImmutableDictionary<string, object>> tagsOutput ? tagsOutput :
                        tagsValue is ImmutableDictionary<string, object> tagsDictionary ? tagsDictionary :
                        throw new InvalidOperationException($"Unexpected tags type: {tagsValue.GetType()}") :
                    ImmutableDictionary<string, object>.Empty;

                    tags = tags.Apply(t => t.SetItems(myTags));
                    return new( args.Args.SetItem("metadata", tags), args.Options);
                    }
                    return null;
                },
            },
        });
    }
}
name: helm-kubernetes-part-two-yaml
description: Demo Code Part 2 of the Tutorial around Helm in Kubernetes
runtime: yaml

resources:
  cert-manager:
    type: kubernetes:helm.sh/v4:Chart
    properties:
      namespace: cert-manager
      chart: oci://registry-1.docker.io/bitnamicharts/cert-manager
      version: "1.3.1"

Deploying the Helm chart

Now run the pulumi up command to preview and deploy the resources you’ve just defined in your project.

Please choose a stack, or create a new one: dev
Previewing update (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/dirien/helm-kubernetes-part-two-typescript/dev/previews/e88dbf07-434c-453c-b49c-dba176957abc

     Type                                                                             Name                                                              Plan       
 +   pulumi:pulumi:Stack                                                              helm-kubernetes-part-two-typescript-dev                           create     
 +   └─ kubernetes:helm.sh/v4:Chart                                                   cert-manager                                                      create     
 +      ├─ kubernetes:core/v1:ServiceAccount                                          cert-manager:cert-manager/cert-manager-controller                 create     
 +      ├─ kubernetes:policy/v1:PodDisruptionBudget                                   cert-manager:cert-manager/cert-manager-cainjector                 create     
 +      ├─ kubernetes:core/v1:ServiceAccount                                          cert-manager:cert-manager/cert-manager-webhook                    create     
 +      ├─ kubernetes:core/v1:ServiceAccount                                          cert-manager:cert-manager/cert-manager-cainjector                 create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:Role                               cert-manager:kube-system/cert-manager-cainjector-leader-election  create     
 +      ├─ kubernetes:admissionregistration.k8s.io/v1:MutatingWebhookConfiguration    cert-manager:cert-manager/cert-manager-webhook                    create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-view                         create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding                        cert-manager:cert-manager/cert-manager-webhook-dynamic-serving    create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding                        cert-manager:kube-system/cert-manager-cainjector-leader-election  create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-certificates      create     
 +      ├─ kubernetes:core/v1:Service                                                 cert-manager:cert-manager/cert-manager-webhook                    create     
 +      ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy                              cert-manager:cert-manager/cert-manager-controller                 create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-webhook-subjectaccessreviews            create     
 +      ├─ kubernetes:core/v1:Service                                                 cert-manager:cert-manager/cert-manager-controller-metrics         create     
 +      ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy                              cert-manager:cert-manager/cert-manager-cainjector                 create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-orders            create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-challenges        create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-issuers           create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-edit                         create     
 +      ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy                              cert-manager:cert-manager/cert-manager-webhook                    create     
 +      ├─ kubernetes:policy/v1:PodDisruptionBudget                                   cert-manager:cert-manager/cert-manager-controller                 create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-orders            create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-approve           create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-clusterissuers    create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-cainjector                              create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-ingress-shim      create     
 +      ├─ kubernetes:admissionregistration.k8s.io/v1:ValidatingWebhookConfiguration  cert-manager:cert-manager/cert-manager-webhook                    create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-webhook-subjectaccessreviews            create     
 +      ├─ kubernetes:policy/v1:PodDisruptionBudget                                   cert-manager:cert-manager/cert-manager-webhook                    create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding                        cert-manager:kube-system/cert-manager-controller-leader-election  create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:Role                               cert-manager:kube-system/cert-manager-controller-leader-election  create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-certificates      create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-challenges        create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-clusterissuers    create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-cainjector                              create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-ingress-shim      create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:Role                               cert-manager:cert-manager/cert-manager-webhook-dynamic-serving    create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-approve           create     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-issuers           create     
 +      ├─ kubernetes:apps/v1:Deployment                                              cert-manager:cert-manager/cert-manager-cainjector                 create     
 +      ├─ kubernetes:apps/v1:Deployment                                              cert-manager:cert-manager/cert-manager-webhook                    create     
 +      └─ kubernetes:apps/v1:Deployment                                              cert-manager:cert-manager/cert-manager-controller                 create     

Resources:
    + 44 to create

Do you want to perform this update? yes
Updating (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/dirien/helm-kubernetes-part-two-typescript/dev/updates/4

     Type                                                                             Name                                                              Status              
 +   pulumi:pulumi:Stack                                                              helm-kubernetes-part-two-typescript-dev                           created (37s)       
 +   ├─ kubernetes:core/v1:Namespace                                                  cert-manager                                                      created (0.23s)     
 +   └─ kubernetes:helm.sh/v4:Chart                                                   cert-manager                                                      created             
 +      ├─ kubernetes:admissionregistration.k8s.io/v1:MutatingWebhookConfiguration    cert-manager:cert-manager/cert-manager-webhook                    created (0.27s)     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:Role                               cert-manager:kube-system/cert-manager-controller-leader-election  created (0.49s)     
 +      ├─ kubernetes:core/v1:Service                                                 cert-manager:cert-manager/cert-manager-controller-metrics         created (11s)       
 +      ├─ kubernetes:core/v1:ServiceAccount                                          cert-manager:cert-manager/cert-manager-controller                 created (0.71s)     
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding                        cert-manager:kube-system/cert-manager-cainjector-leader-election  created (2s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-certificates      created (3s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-clusterissuers    created (4s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-ingress-shim      created (4s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-cainjector                              created (1s)        
 +      ├─ kubernetes:core/v1:ServiceAccount                                          cert-manager:cert-manager/cert-manager-webhook                    created (1s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:Role                               cert-manager:kube-system/cert-manager-cainjector-leader-election  created (1s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:Role                               cert-manager:cert-manager/cert-manager-webhook-dynamic-serving    created (2s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-challenges        created (3s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding                        cert-manager:kube-system/cert-manager-controller-leader-election  created (3s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-approve           created (4s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-approve           created (7s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-orders            created (6s)        
 +      ├─ kubernetes:core/v1:ServiceAccount                                          cert-manager:cert-manager/cert-manager-cainjector                 created (7s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-edit                         created (5s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-certificates      created (6s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-issuers           created (6s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-controller-controller-orders            created (5s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding                        cert-manager:cert-manager/cert-manager-webhook-dynamic-serving    created (7s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-clusterissuers    created (8s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-webhook-subjectaccessreviews            created (8s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-issuers           created (8s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-cainjector                              created (5s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-view                         created (9s)        
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-ingress-shim      created (7s)        
 +      ├─ kubernetes:policy/v1:PodDisruptionBudget                                   cert-manager:cert-manager/cert-manager-cainjector                 created (9s)        
 +      ├─ kubernetes:admissionregistration.k8s.io/v1:ValidatingWebhookConfiguration  cert-manager:cert-manager/cert-manager-webhook                    created (11s)       
 +      ├─ kubernetes:core/v1:Service                                                 cert-manager:cert-manager/cert-manager-webhook                    created (19s)       
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding                 cert-manager:cert-manager-webhook-subjectaccessreviews            created (10s)       
 +      ├─ kubernetes:policy/v1:PodDisruptionBudget                                   cert-manager:cert-manager/cert-manager-webhook                    created (11s)       
 +      ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole                        cert-manager:cert-manager-controller-controller-challenges        created (10s)       
 +      ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy                              cert-manager:cert-manager/cert-manager-cainjector                 created (12s)       
 +      ├─ kubernetes:policy/v1:PodDisruptionBudget                                   cert-manager:cert-manager/cert-manager-controller                 created (10s)       
 +      ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy                              cert-manager:cert-manager/cert-manager-controller                 created (12s)       
 +      ├─ kubernetes:apps/v1:Deployment                                              cert-manager:cert-manager/cert-manager-cainjector                 created (17s)       
 +      ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy                              cert-manager:cert-manager/cert-manager-webhook                    created (13s)       
 +      ├─ kubernetes:apps/v1:Deployment                                              cert-manager:cert-manager/cert-manager-webhook                    created (19s)       
 +      └─ kubernetes:apps/v1:Deployment                                              cert-manager:cert-manager/cert-manager-controller                 created (19s)       

Resources:
    + 45 created

Duration: 39s

As you can see, the Pulumi CLI shows all the resources that will be created in your Kubernetes cluster. This is due to the Chart resource that renders the templates from the Helm chart and then manages the objects directly with the Pulumi Kubernetes provider.

With following command you can check the resources created by the Helm chart where also successful annotated with the cost-center label.

 kubectl get svc -n cert-manager -o jsonpath="{..metadata.annotations}" | tr ' ' '\n' | sort | uniq 

And you should see the resources that were created by the Helm chart.

{"cost-center":"12345"}

Housekeeping

Before moving on, tear down the resources that are part of your stack to avoid incurring any charges.

  1. Run pulumi destroy to tear down all resources. You’ll be prompted to make sure you really want to delete these resources. A destroy operation may take some time, since Pulumi waits for the resources to finish shutting down before it considers the destroy operation to be complete.
  2. To delete the stack itself, run pulumi stack rm. Note that this command deletes all deployment history from the Pulumi Service.

Next steps

In this tutorial, you learned how to install Helm on Kubernetes using the Kubernetes provider from Pulumi and the Release resource.