Appendix

Stack authoring

So you want to share your favorite stack and best practices with others? If you want to become a stack curator, this page is for you.

You will learn how to create a stack template from scratch and how your users can use it.

Prerequisites

  • To complete this tutorial, make sure you understood the end-user flows of Gimlet Stack, and you understand the concepts thoroughly.

  • An empty public git repository

What defines a stack

  • a stack lives in a dedicated git repository
  • a stack is a mix of Kubernetes resources, configmaps, HelmRelease resources placed in the git repo's root
  • the resources are Golang templates, and can resolve variables that are defined in the stack.yaml by the end-user
  • a stack has a stack-definition.yaml in the git repository root
    • that contains the stack meta data
    • component metadata, name, version, and a one-pager per component that helps the user getting started with the component
    • component schema that is a subset of the component's Helm Chart's values.schema.json
    • the ui-schema, that is a helm-ui.json schema file described in this blog post: https://gimlet.io/blog/helm-react-ui-a-react-component-to-render-ui-for-helm-charts/#the-helm-ui.jso

Gimlet Stack maintains a reference stack implementation in the https://github.com/gimlet-io/gimlet-stack-reference repository.

Prepare your first stack

Let's start with creating a minimal stack that we can use to experience the stack authoring process.

Fork the https://github.com/gimlet-io/gimlet-stack-minimal git repository and clone it.

git clone git@github.com:gimlet-io/gimlet-stack-minimal.git
cd gimlet-stack-minimal

This repository holds a minimal stack with a single Nginx component:

  • stack-definition.yaml to hold the stack schema and UI information
  • helm-releases folder to hold the Nginx HelmRelease file
  • helm-repositories folder to hold the HelmRepository for the Nginx Helm chart
  • manifests folder to hold a plain manifest with the Kubernetes namespace definition
  • .sourceignore file to ignore deploying certain files from the gitops repo. Since stack.yaml is stored there, Flux needs to know to disregard it
  • .stackignore file to ignore files from getting to to the gitops repo during gimlet stack generate. You typically want to ignore README.md and other documentation files
➜  gimlet-stack-minimal git:(main) tree
.
├── helm-releases
│   └── ingress-nginx.yaml
├── helm-repositories
│   └── ingress-nginx.yaml
├── manifests
│   └── namespace.yaml
├── README.md
├── .sourceignore
├── stack-definition.yaml
└── .stackignore

3 directories, 7 files

A minimal Gimlet Stack

Stack files are Golang templates

The values end-users specify in the stack.yaml file are available in the stack template to drive rendering.

The bellow example shows that the ingress-nginx HelmRelease only rendered if .nginx.enabled is set to true, and it also adds the .nginx.host variable as an annotation.


# helm-releases/ingress-nginx.yaml
{{ if .nginx.enabled -}}
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: ingress-nginx
  namespace: infrastructure
spec:
...
  values:
    controller:
      annotations:
        service.beta.kubernetes.io/do-loadbalancer-enable-proxy-protocol: "true"
        service.beta.kubernetes.io/do-loadbalancer-hostname: "{{ .nginx.host }}"
      config:
        use-proxy-protocol: "true"
{{- end }}

What variables are available for a stack is stored in the stack-definiton.yaml file.

The stack definition

The stack-definition.yaml holds all stack components, their description and all possible configuration options.

# stack-definition.yaml
---
name: Gimlet Stack Minimal
description: A minimal stack that we can use to experience the stack authoring process
categories:
  - name: '⬅️Ingress'
    id: ingress
components:
  - name: Nginx
    variable: nginx
    category: ingress
    logo: https://raw.githubusercontent.com/gimlet-io/gimlet-stack-reference/main/assets/nginx.png
    description: ''
    onePager:
      An Nginx proxy server that routes traffic to your applications based on
      the host name or path.
    schema: |-
      ...
    uiSchema: |-
      ...

The components are

  • organized in categories
  • they have a name, logo and other meta information
  • and they define a JSON schema for their values
  • and a uiSchema for the gimlet stack configure GUI

Let's see how you can add a new component to this minimal stack.

Let's extend the stack now

Let's add Grafana Loki now as a new component to the stack:

  • we extended the categories list
  • and added the Loki component
# stack-definition.yaml
---
name: Gimlet Stack Minimal
description: A minimal stack that we can use to experience the stack authoring process
categories:
  - name: "⬅️Ingress"
    id: ingress
+  - name: "📑 Logging"
+    id: logging
components:
  - name: Nginx
    variable: nginx
    category: ingress
    logo: https://raw.githubusercontent.com/gimlet-io/gimlet-stack-reference/main/assets/nginx.png
    description: ''
    onePager: An Nginx proxy server that routes traffic to your applications based on
      the host name or path.
    schema: |-
      ...
    uiSchema: |-
      ...
+  - name: Loki
+    variable: loki
+    category: logging
+    logo: https://raw.githubusercontent.com/gimlet-io/gimlet-stack-reference/main/assets/loki.png
+    description: ''
+    onePager: ''
+    schema: |-
+      ...
+    uiSchema: |-
+      ...

Now that we extended the stack definition, let's add the Kubernetes manifests to the stack template

Adding the Loki manifests to the stack template

We add two files, the HelmRepository and a HelmRelease resources for Loki.

If you are not familiar with the mentioned resource types, they are from the Flux project. You can add any regular Kubernetes manifest, we add now these custom resources as they achieve the installation of a Helm chart in a very concise way.


# helm-repositories/grafana.yaml
{{ if or .loki.enabled-}}
---
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmRepository
metadata:
  name: grafana
  namespace: infrastructure
spec:
  interval: 60m
  url: https://grafana.github.io/helm-charts
{{- end }}

# helm-releases/loki.yaml
{{ if .loki.enabled -}}
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: loki
  namespace: infrastructure
spec:
  interval: 60m
  releaseName: loki
  chart:
    spec:
      chart: loki-stack
      version: 2.3.1
      sourceRef:
        kind: HelmRepository
        name: grafana
      interval: 10m
  values:
    grafana:
      enabled: false
    prometheus:
      enabled: false
{{- end }}

The above yaml can be arbitrary, whatever files your stack needs. The only requirement is that Kubernetes should understand the rendered template.

In this example we have one template variable: .loki.enabled. Let's see now how this can be captured in the stack-definition.yaml

Explaining the schema and uiSchema fields

Every component in the stack-definition.yaml needs to define its variable schema and the UI schema, so it can be properly rendered on the gimlet stack configure UI.

  - name: Loki
    variable: loki
    category: logging
    logo: https://raw.githubusercontent.com/gimlet-io/gimlet-stack-reference/main/assets/loki.png
    description: ''
    onePager: ''
    schema: |-
+      {
+        "$schema": "http://json-schema.org/draft-07/schema",
+        "$id": "http://example.com/example.json",
+        "type": "object",
+        "title": "The root schema",
+        "description": "The root schema comprises the entire JSON document.",
+        "properties": {
+          "enabled": {
+            "$id": "#/properties/enabled",
+            "type": "boolean",
+            "title": "Enabled"
+          }
+        }
+      }
    uiSchema: |-
+      [
+        {
+          "schemaIDs": [
+            "#/properties/enabled"
+          ],
+          "uiSchema": {},
+          "metaData": {}
+        }
+      ]

schema is a regular JSON schema definition. While it is verbose and cumbersome to maintain, you can edit small schemas by hand. This tutorial doesn't try to explain JSON schemas, instead we recommend that you get inspired with the possibilities from the reference stack.

For Loki, we defined a single boolean field, and the required boilerplate.

uiSchema is a Gimlet specific format that drives the UI rendering for stack-configure. The format was introduced on the blog, and for Loki all we did is that we told the UI to render the enabled property that we defined in the JSON schema. For more feature examples check out the UI schema of the OneChart Helm chart.

Testing the extended stack

In order to test the extended stack, make a commit and push the stack to git.

Then you can verify the generated UI with, just make sure you point it to your git repository

gimlet stack configure \
  --stack-repo git@github.com:gimlet-io/gimlet-stack-minimal.git

And you can verify the rendered template with:

gimlet stack generate

You should see the following UI:

The extended Gimlet Stack

You can find the extended stack on the authoring-your-first-stack branch, and you can also render it straight from that branch - if you want to verify your work:

gimlet stack configure \
  --stack-repo "git@github.com:gimlet-io/gimlet-stack-minimal.git?branch=authoring-your-first-stack"
Previous
Flux CRDs