How to Check that a Kubernetes API Resource Exists when Using Helm

Photo by Markus Winkler on Unsplash

When developing Helm charts, you may encounter a use case where you need to create a specific Kubernetes resource only if that particular resource is registered with the API. For example, imagine you maintain a Helm chart that should be made deployable to different Kubernetes distributions. In your Helm chart, you want your application to be externally accessible to clients outside of your cluster. Many Kubernetes distributions achieve this by using an Ingress resource. However, in OpenShift (Red Hat’s Kubernetes distro), you would likely create a Route instead. How can you create a Helm chart that creates either an Ingress or a Route for external access, depending on if the resource is available to the given cluster?

To do this, Helm provides the Capabilities.APIVersions and Capabilities.APIVersions.Has built-in objects that can help you conditionally create resources depending on if their API endpoint exists or not. Let’s look at an example of how the Capabilities.APIVersions object works.

The Capabilities.APIVersions Object

When the Capabilities.APIVersions object is invoked directly, each registered API resource and version will be displayed as a String. Consider the following template:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}
data:
  apiVersions.txt: |
    {{ .Capabilities.APIVersions }}

This would expand to reveal each of the API versions and resources that can be created within the target cluster.

→ helm install test-chart . --dry-run
...
MANIFEST:
---
# Source: test-chart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-chart
data:
  apiVersions.txt: |
    [rbac.authorization.k8s.io/v1beta1/RoleBinding v1/PersistentVolume coordination.k8s.io/v1beta1/Lease v1/Eviction apps/v1/ControllerRevision rbac.authorization.k8s.io/v1/ClusterRole v1 v1/NodeProxyOptions apiextensions.k8s.io/v1/CustomResourceDefinition v1/Secret rbac.authorization.k8s.io/v1/Role v1/ResourceQuota apps/v1/ReplicaSet authorization.k8s.io/v1beta1/SubjectAccessReview networking.k8s.io/v1beta1 v1/PersistentVolumeClaim v1/ConfigMap authentication.k8s.io/v1/TokenReview networking.k8s.io/v1beta1/IngressClass networking.k8s.io/v1beta1/Ingress storage.k8s.io/v1beta1/StorageClass rbac.authorization.k8s.io/v1beta1 admissionregistration.k8s.io/v1beta1 networking.k8s.io/v1 admissionregistration.k8s.io/v1/ValidatingWebhookConfiguration storage.k8s.io/v1beta1/CSIDriver authentication.k8s.io/v1beta1 coordination.k8s.io/v1beta1 rbac.authorization.k8s.io/v1 storage.k8s.io/v1/CSIDriver storage.k8s.io/v1beta1/CSINode scheduling.k8s.io/v1beta1 v1/ReplicationController autoscaling/v1 v1/Scale storage.k8s.io/v1/StorageClass discovery.k8s.io/v1beta1/EndpointSlice events.k8s.io/v1beta1 authorization.k8s.io/v1 apiregistration.k8s.io/v1/APIService autoscaling/v2beta2/HorizontalPodAutoscaler policy/v1beta1/PodSecurityPolicy admissionregistration.k8s.io/v1/MutatingWebhookConfiguration apiregistration.k8s.io/v1beta1 apps/v1 apps/v1/Deployment rbac.authorization.k8s.io/v1/ClusterRoleBinding authorization.k8s.io/v1beta1 apiregistration.k8s.io/v1beta1/APIService v1/ComponentStatus scheduling.k8s.io/v1 v1/Binding certificates.k8s.io/v1beta1/CertificateSigningRequest storage.k8s.io/v1 authentication.k8s.io/v1beta1/TokenReview scheduling.k8s.io/v1/PriorityClass authentication.k8s.io/v1 storage.k8s.io/v1/CSINode authorization.k8s.io/v1/LocalSubjectAccessReview authorization.k8s.io/v1/SelfSubjectAccessReview autoscaling/v1/HorizontalPodAutoscaler autoscaling/v2beta1/HorizontalPodAutoscaler rbac.authorization.k8s.io/v1beta1/ClusterRole v1/LimitRange apps/v1/DaemonSet v1/Service extensions/v1beta1/Ingress authorization.k8s.io/v1/SelfSubjectRulesReview v1/Endpoints v1/PodPortForwardOptions apiextensions.k8s.io/v1beta1 autoscaling/v2beta2 storage.k8s.io/v1beta1 batch/v1beta1/CronJob admissionregistration.k8s.io/v1beta1/MutatingWebhookConfiguration discovery.k8s.io/v1beta1 apps/v1/Scale events.k8s.io/v1beta1/Event authorization.k8s.io/v1beta1/SelfSubjectAccessReview v1/Event v1/PodAttachOptions scheduling.k8s.io/v1beta1/PriorityClass batch/v1/Job rbac.authorization.k8s.io/v1beta1/Role coordination.k8s.io/v1 v1/PodTemplate v1/ServiceAccount extensions/v1beta1 policy/v1beta1 v1/Namespace apps/v1/StatefulSet node.k8s.io/v1beta1 authorization.k8s.io/v1beta1/LocalSubjectAccessReview admissionregistration.k8s.io/v1beta1/ValidatingWebhookConfiguration node.k8s.io/v1beta1/RuntimeClass v1/Node v1/ServiceProxyOptions apiextensions.k8s.io/v1beta1/CustomResourceDefinition autoscaling/v2beta1 authorization.k8s.io/v1beta1/SelfSubjectRulesReview v1/Pod networking.k8s.io/v1/NetworkPolicy rbac.authorization.k8s.io/v1/RoleBinding coordination.k8s.io/v1/Lease batch/v1beta1 apiextensions.k8s.io/v1 authorization.k8s.io/v1/SubjectAccessReview storage.k8s.io/v1beta1/VolumeAttachment v1/PodExecOptions v1/PodProxyOptions policy/v1beta1/PodDisruptionBudget rbac.authorization.k8s.io/v1beta1/ClusterRoleBinding batch/v1 certificates.k8s.io/v1beta1 storage.k8s.io/v1/VolumeAttachment apiregistration.k8s.io/v1 admissionregistration.k8s.io/v1]

As you can see, there are quite a few resources that get output by the Capabilities.APIVersions object. To make this more useful, you can use Capabilities.APIVersions.Has to check that a specific API resource or version is available.

Using Capabilities.APIVersions.Has to Check if a Resource is Registered

Using Capabilities.APIVerions.Has will allow you to check for the existence of a specific API version or resource.

Returning to our Ingress vs Route example, let’s say that you want to install a Route if the “route.openshift.io/v1/Route” resource is available, but if it’s not, you’ll check if the networking.k8s.io/v1beta1/Ingress resource is available instead to create an Ingress. The following template demonstrates this logic.

{{- if .Capabilities.APIVersions.Has "route.openshift.io/v1/Route" }}
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: {{ .Release.Name }}
spec:
  port:
    targetPort: 8080
  to:
    kind: Service
    name: my-app
    weight: 100
{{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1/Ingress" }}
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: {{ .Release.Name }}
spec:
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              serviceName: my-app
              servicePort: 8080
{{- end }}

This template would allow you to create a Route if you’re in an OpenShift cluster (that is if route.openshift.io/v1/Route is available) and will otherwise enable you to create an Ingress for external access.

Thanks for Reading!

Next time you need to decide which resource to create based on resource availability, be sure to use the Capabilities.APIVersions.Has built-in object to quickly template this logic in your Helm chart. If you need to see a full list of API versions and resources, you can also use the Capabilities.APIVersions object on its own.

Austin Dewey

Austin Dewey is a DevOps engineer focused on delivering a streamlined developer experience on cloud and container technologies. Austin started his career with Red Hat’s consulting organization, where he helped drive success at many different Fortune 500 companies by automating deployments on Red Hat’s Kubernetes-based PaaS, OpenShift Container Platform. Currently, Austin works at fintech startup Prime Trust, building automation to scale financial infrastructure and support developers on Kubernetes and AWS. Austin is the author of "Learn Helm", a book focused on packaging and delivering applications to Kubernetes, and he enjoys writing about open source technologies at his blog in his free time, austindewey.com.

Leave a Reply