Building a no-code Helm UI with Windmill - Part 1
Tags: windmill, kubernetes, serverless, lowcode
Published: 2023-12-06 15:28:08

Hey everyone! Today I would like to introduce you to [Windmill](https://www.windmill.dev/), a serverless platform enabling you to write scripts in many languages, chain them together, build an UI around them and much, much more. Sounds too good to be true? Well let’s put it to the test by building together a dynamic UI for managing Helm charts in a Kubernetes cluster.

## Introduction

There's no doubt that you've found yourself in a situation like this at some point: you’ve been asked to build a quick workflow, where you had to chain a few actions, send a notification somewhere or a piece of data into a system. You have the code ready but then, all of that needs to be packaged and deployed (after tests), and if you’re lucky, you already have all the resources and systems for that part of the process.

But releasing your code into the world is only the initial step in a larger process. Once the code is out there, it needs to be scheduled, executed and observed. This doesn't just happen on its own and you need systems in place to do just that.

This is where serverless Cloud solutions such as [“AWS Step Functions”](https://aws.amazon.com/fr/step-functions/) caters to these needs. Step Functions allows you to chain Lambdas, and call upon AWS services with extra logic to your workflow. In essence, it provides a robust platform that allows your code to function as intended, while also offering the necessary tools to monitor and optimize its performance.

![AWS Step Function Workflow Studio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zjq4wufc3mbgwuv7o113.png)

> See https://aws.amazon.com/fr/blogs/aws/new-aws-step-functions-workflow-studio-a-low-code-visual-tool-for-building-state-machines/ for more info! 

Cloud solutions are a great fit indeed, and they’ll probably be good enough for many use cases. But today we’ll talk about an entire platform centered around these workflows, a platform where you could share actions with your coworkers, tie them together in workflows and build UIs around them. 

And that’s where Windmill comes in, with three key concepts:

- Scripts, which are re-usable pieces of code that have inputs and outputs
- Flows, which tie scripts together with logic, branching and retries
- Apps, made of dynamic React components which can trigger and display scripts and flows

With these three building blocks, you’ll be able to create powerful and reliable workflows, let’s put it to the test.

I’ve created a local cluster with [K3S](https://k3s.io/) and installing Windmill could not be simpler with just one chart to configure, which already has sane defaults to get started. For this demo we will also configure workers to passthrough environment variables to our scripts so that they have access to the Kubernetes API server for later.

```bash
helm repo add windmill https://windmill-labs.github.io/windmill-helm-charts/
helm show values windmill/windmill > values.yaml
yq -i '.windmill.workerGroups[].extraEnv += {"name": "WHITELIST_ENVS", "value": "KUBERNETES_SERVICE_HOST,KUBERNETES_SERVICE_PORT"}' values.yaml
helm install windmill windmill/windmill	--namespace=windmill --create-namespace -f values.yaml --set windmill.appReplicas=1 --set windmill.lsbReplicas=1
```

Note: see the [Chart’s README](https://github.com/windmill-labs/windmill-helm-charts/blob/main/README.md) for all potential values. I recommend managing them finely, with dedicated service accounts for example.

We’ll then watch the pods being created:

```bash
$ kubectl get pods
NAME                                       READY   STATUS    RESTARTS   AGE
windmill-app-567858d877-gqp26              0/1     Running   0          32s
windmill-lsp-7bd57bfdfc-bdfg8              1/1     Running   0          32s
windmill-postgresql-0                      1/1     Running   0          32s
windmill-workers-default-dc4cc4d76-5wvtl   1/1     Running   0          32s
windmill-workers-native-8df7667f-6xl29     1/1     Running   0          32s                   0/1     ContainerCreating   0          20s
```

While we wait, let’s explore a bit more the architecture of the project and what’s being deployed:

- We have a PostgreSQL instance starting
- An app deployment for the UI and API
- Several workers deployments to execute our scripts
- And an LSP server to provide autocompletions

Once running, you’ll notice an Ingress configuration has also been created, you can adjust the hostname either via values or via the initial configuration steps. Open a browser to your ingress and you’ll be greeted with the default login screen, it will be `admin@windmill.dev:changeme`:

![Windmill Login screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wruwva6hyfdohxj4lzc3.png)

And after a few settings that you can tweak, you’ll be prompted to change the admin & password email and finally you’ll be able to create a new workspace that will contains all your workflows.

⚠️ There is an opt-out Telemetry option you might not want to miss if testing in a sensitive environment, but it’s also helpful data for the project to grow! 

And there you go, we’re in !

![Windmill homepage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/au0mxyetv7m9vd3nbd2l.png)

## Building an Helm UI

To showcase the basic set of features of Windmill, let’s build ourselves a quick Helm UI, that our users could then use to list and rollback releases. On rollback, we’ll also send a notification to Slack. 

### Listing namespaces

We’ll need a script to list our cluster namespaces, and then the releases it contains.

Click on the “+ Script” button at the top of the window, we’ll select Python as the runtime, give it a good name, and you can click on the editor on the left to get the Settings window out of the way.

We’ll import the [`kr8s`](https://github.com/kr8s-org/kr8s) package to interact with the cluster, and when you’ll remove arguments from the `main` function, you will also see them disappear from the input section! Quite neat.

Let’s write a script to return a list of current namespaces:

```python
import kr8s

def main():
    return [ns.name for ns in kr8s.get("namespaces")]
```

And by pressing CTRL+Enter, execution will start and will error-out:

![Execution output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0jruuq4tyzzjwixjnlxp.png)

Which should not be surprising as we did not configure additional permissions to Windmill workers.

Let’s add a quick ClusterRole and ClusterRoleBinding to the `windmill` user. In practice, we would start adding a custom ServiceAccount for this dedicated worker pool:

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: windmill-k8s
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: windmill-k8s
subjects:
- kind: ServiceAccount
  name: windmill
  namespace: windmill
roleRef:
  kind: ClusterRole
  name: windmill-k8s
  apiGroup: rbac.authorization.k8s.io
```

Re-running the job, we will see the expected result:

![Successful execution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w91e6yni1h3mx4812bxs.png)

Note the "found cached resolution" debug line. It indicates that Windmill computed and cached all the dependencies necessary to run this script. This will prevent subsequent pip install actions until changes are made.

You can now click on the Deploy button at the top right and CTRL+ENTER on your keyboard to execute the script, where you'll get more details on the duration, memory usage and status.

### Listing Helm releases

Since Helm is included by default in the worker's Docker image (thanks for that!), we're able to create a new Bash script:

```bash
# shellcheck shell=bash
namespace="$1"
helm list -n $namespace -o json > ./result.json
```

Our script is accepting an argument “namespace” and we’ll write the result to `result.json` which will be parsed by Windmill and made available.

We will now have to adjust our RBAC permissions to grant read on the secrets:

```bash
kubectl patch clusterrole windmill-k8s --type='json' -p='[{"op": "add", "path": "/rules/-", "value": {"apiGroups": [""], "resources": ["secrets"], "verbs": ["get", "list"]}}]'
```

And by saving and running our script, we’ll see our list of releases:

![Helm releases output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n69m5okrnx58t2turdda.png)

Notice how we automatically had an input field for our namespace? Windmill automatically built a quick UI for our script so we're able to run them whenever we want.

### Building an Helm UI

With our first scripts ready, we are now able to chain them together to build an UI! Back on the homepage of the workspace, let’s click on the “+ App” button which will open the App editor.

There are many things to present of this editor, but I will refer you to the [Youtube playlist](https://www.youtube.com/playlist?list=PLITtG6lMX-nJRtJMQnYrPf5eubN2xWjkA) made by Windmill authors and the [documentation website](https://www.windmill.dev/docs/getting_started/apps_quickstart).

For now, let’s focus on our quick & dirty! Scroll down the list of available components and drag and drop the “Select” component.

Now we need to prepare the list of namespaces for this component. At the bottom of the editor you will find the “Background Runnable” section, click on the + (plus) sign to create a new runnable, click on “Select a script or flow” then on the opened window, select the “Workspace scripts”, then finally, your script listing namespaces.

![Background runnable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8yyo3s0c6pgs0k9137t2.png)

Note: you might have not missed the “Frontend” language section when creating a new background runnable, you can indeed add extra Javascript code that will be executed in the browser.

The Select input form expect data in a specific format, so we’ll add a transformer, which is just a quick Javascript function to transform our namespaces into what’s expected. When selecting the runnable, you will notice on the right in the settings panel, a “Transformer” section.

![Transformer section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4rm8uphseb36w1bz2uqp.png)

Click on the “Add” button and in the new script view, transform the result then click on “Run”:

```js
return result.map((result) => ({label: result, value: result}))
```

You can inspect the current state of the app and view our reflected data model.

Let’s now plug our Select input. Click on the component, and on its configuration tab, click on the little plug icon next to “Items”, this will allow us to configure the data source for this select. Click on the “result” button in the `bg_0` output and the select will be automatically populated!

![Select populated](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vvuo97qgsmujo23pkykb.png)

Next up, we’ll do the same thing with the Helm releases. We create a new background runnable, we select our “List Helm releases” script and this time we’ll notice we’re expected to configure the input. Again, click on the “Connect” plug symbol and we’ll select the result of our select component in the Outputs panel at the left (or you can also write `a.result`) if you’re in a hurry. Make sure to disable the “Run on start and app refresh” option as we’ll need to have the namespaces first.

Now, every changes to the select value will make Windmill execute again the script with the proper variable, making that data available. Let’s display it with the Table component!

Again, drag & drop the Table component, and select the plug symbol. Select the result of `bg_1` which is our list of Helm releases, and boom, you’ve got a table with your releases:

![Table of releases](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6lstscjkxwxrw2jwfip1.png)

When you select another namespace, the table will refresh with the releases from this namespace. Adjust width and re-organization or add more components if you wish, then you can click on Deploy to get a pretty view that you can share with coworkers:

![Helm releases](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ikjggtawum82kbqz3cj7.png)

Great success! You can re-order columns or even hide some if you want by configuring the component.

## Wrapping up

And that's it for this first part! I hope I was able to share with you the same excitation I had when I first started using this tool! In the next and final part, we'll look at how we can implement a Flow and add a Rollback button to our table. See you!

### Resources

The following resources might be of interest:

* [Windmill Youtube channel](https://www.youtube.com/@WindmillDev)
* [Windmill documentation](https://www.windmill.dev/docs/intro)
* Trevor Sullivan has made [a series of videos](https://www.youtube.com/playlist?list=PLDbRgZ0OOEpWT68Jn_I-B3KpZ9xp2Vukn
) using Windmill if you want to deep dive