Skip to content

MacroPower/kat

Repository files navigation

kat

Go Reference Go Report Card GitHub Downloads Latest tag License

kat provides a terminal UI for rendering, validating, and displaying local Kubernetes manifests. It eliminates the frustrating cycle of manually running commands, scrolling through endless output, and constantly losing context when working with Helm charts, Kustomize overlays, and other manifest generators.


❀️ Made with bubble tea, glow, and chroma.

✨ Features

  • πŸ”οΈ Manifest browsing - Navigate hundreds of rendered manifests with fuzzy search and filtering, no more endless scrolling through terminal output
  • ⚑️ Live reload - Use --watch to automatically re-render when you modify source files, without losing your current context
  • πŸ› Error handling - Rendering and validation errors are displayed as overlays and disappear if reloading resolves the error
  • 🎯 Project detection - Automatically detect Helm charts, Kustomize projects, and custom manifest generators using powerful CEL expressions
  • πŸ§ͺ Tool integration - Define profiles for any manifest generator (Helm, Kustomize, CUE, KCL, Jsonnet, etc.) with pre/post-render hooks
  • πŸ”Œ Plugin system - Create custom keybind-triggered commands for common tasks that can't run on hooks, like dry-runs or diffs
  • βœ… Custom validation - Run tools like kubeconform, kyverno, or custom validators automatically on rendered output
  • 🎨 Beautiful UI - Syntax-highlighted YAML with customizable themes and keybindings that match your preferences

πŸ“¦ Installation

Homebrew

brew install macropower/tap/kat

Go

go install github.com/macropower/kat/cmd/kat@latest

Docker

Docker images are published to ghcr.io/macropower.

All images are configured with WORKDIR=/data, so you can mount your current directory there to run kat against your local files.

Run the latest alpine image:

docker run -it -v .:/data -e TERM=$TERM ghcr.io/macropower/kat:latest-alpine

Run the latest debian image:

docker run -it -v .:/data -e TERM=$TERM ghcr.io/macropower/kat:latest-debian

The default config is located at /config/kat/config.yaml, and you can override it by mounting your own configuration file at that path.

There is also a scratch image that contains only the kat binary, which is useful when you want to build your own image (which I generally recommend doing):

FROM alpine:latest
COPY --from=ghcr.io/macropower/kat:latest /kat /usr/local/bin/kat
# Add whatever customization you need here.
ENTRYPOINT ["/usr/local/bin/kat"]

Nix

You can install kat using my NUR.

With nix-env:

nix-env -iA kat -f https://github.com/macropower/nur-packages/archive/main.tar.gz

With nix-shell:

nix-shell -A kat https://github.com/macropower/nur-packages/archive/main.tar.gz

With your flake.nix:

{
  inputs = {
    macropower.url = "github:macropower/nur-packages";
  };
  # Reference the package as `inputs.macropower.packages.<system>.kat`
}

With devbox:

devbox add github:macropower/nur-packages#kat

GitHub CLI

gh release download -R macropower/kat -p "kat_$(uname -s)_$(uname -m).tar.gz" -O - | tar -xz

And then move kat to a directory in your PATH.

Curl

curl -s https://api.github.com/repos/macropower/kat/releases/latest | \
  jq -r ".assets[] |
    select(.name | test(\"kat_$(uname -s)_$(uname -m).tar.gz\")) |
    .browser_download_url" | \
  xargs curl -L | tar -xz

And then move kat to a directory in your PATH.

Manual

You can download binaries from releases.

πŸ” Verification

You can verify the authenticity and integrity of kat releases.

See verification for more details.

πŸš€ Usage

Show help:

kat --help

Render a project in the current directory:

kat

Render a project and enable watch (live reloading):

kat -w

Render a project in a specific directory:

kat ./example/helm

Render a project in a specific directory using the ks profile:

kat ./example/kustomize ks

Render a project and override the profile arguments:

kat ./example/kustomize ks -- build . --enable-helm

Render a project with command passthrough:

kat ./example/helm task -- helm:render

Render using data from stdin:

cat ./example/kustomize/resources.yaml | kat -f -

βš™οΈ Configuration

You can use kat --write-config to generate a default configuration file at ~/.config/kat/config.yaml. This file allows you to customize the behavior of kat, such as the UI style, keybindings, rules for project detection, and profiles for rendering different types of projects.

Alternatively, you can find the default configuration file in pkg/config/config.yaml.

πŸ› οΈ Rules and Profiles

You can customize how kat detects and renders different types of projects using rules and profiles in the configuration file. This system uses CEL (Common Expression Language) expressions to provide flexible file matching and processing.

🎯 Rules

Rules determine which profile should be used. Each rule contains:

  • match (required): A CEL expression that returns true if the rule should be applied
  • profile (required): The name of the profile to use when this rule matches

Rules use boolean CEL expressions with access to:

  • files (list): All file paths in the directory
  • dir (string): The directory path being processed
rules:
  - # Select the Helm profile if any Helm chart files exist
    match: >-
      files.exists(f, pathBase(f) in ["Chart.yaml", "Chart.yml"])
    profile: helm

  - # Select the Kustomize profile if any Kustomization files exist
    match: >-
      files.exists(f, pathBase(f) in ["kustomization.yaml", "kustomization.yml"])
    profile: ks

  - # Fallback: select the YAML profile if any YAML files exist
    match: >-
      files.exists(f, pathExt(f) in [".yaml", ".yml"])
    profile: yaml

🎭 Profiles

Profiles define how to render projects. They can be automatically selected by rules, or manually specified when kat is invoked. Each profile contains:

  • command (required): The command to execute
  • args: Arguments to pass to the command
  • env: List of environment variables for the command
  • envFrom: List of sources for environment variables
  • source: Define which files to watch for changes (when watch is enabled)
  • ui: UI configuration overrides
  • hooks: Initialization and rendering hooks
    • init hooks are executed once when kat is initialized
    • preRender hooks are executed before the profile's command is run
    • postRender hooks are executed after the profile's command has run, and are provided the rendered output via stdin
  • plugins: Custom commands that can be executed on-demand with keybinds
    • description (required): Human-readable description of what the plugin does
    • keys (required): Array of key bindings that trigger the plugin
    • command (required): The command to execute
    • args: Arguments to pass to the command

Profile source expressions use list-returning CEL expressions with the same variables as rules.

profiles:
  helm:
    command: helm
    args: [template, ., --generate-name]
    source: >-
      files.filter(f, pathExt(f) in [".yaml", ".yml", ".tpl"])
    envFrom:
      - callerRef:
          pattern: "^HELM_.+"
    ui:
      theme: dracula
    hooks:
      init:
        - command: helm
          args: [version, --short]
      preRender:
        - command: helm
          args: [dependency, build]
          envFrom:
            - callerRef:
                pattern: "^HELM_.+"
      postRender:
        # Pass the rendered manifests via stdin to `kubeconform`.
        - command: kubeconform
          args: [-strict, -summary]
    plugins:
      dry-run:
        command: helm
        args: [install, ., -g, --dry-run]
        envFrom:
          - callerRef:
              pattern: "^HELM_.+"
        description: invoke helm dry-run
        keys:
          - code: ctrl+r
            alias: βŒƒr

  ks:
    command: kustomize
    args: [build, .]
    source: >-
      files.filter(f, pathExt(f) in [".yaml", ".yml"])
    env:
      - name: KUSTOMIZE_ENABLE_ALPHA_COMMANDS
        value: "true"
    ui:
      compact: true
      theme: tokyonight-storm
    hooks:
      init:
        - command: kustomize
          args: [version]

🧩 CEL Functions

kat provides custom CEL functions for file path operations:

  • pathBase(string): Returns the filename (e.g., "Chart.yaml")
  • pathExt(string): Returns the file extension (e.g., ".yaml")
  • pathDir(string): Returns the directory path
  • yamlPath(file, path): Reads a YAML file and extracts a value using a YAML path expression

You can combine these with CEL's built-in functions like exists(), filter(), in, contains(), matches(), and logical operators.

Example:

rules:
  - match: >-
      files.exists(f,
        pathBase(f) == "Chart.yaml" &&
        yamlPath(f, "$.apiVersion") == "v2")
    profile: helm

profiles:
  helm:
    command: helm
    args: [template, ., --generate-name]
    source: >-
      files.filter(f,
        pathExt(f) in [".yaml", ".yml", ".tpl"])

For more details on CEL expressions and examples, see the CEL documentation.

πŸ”₯ DRY Configuration

The kat configuration supports YAML anchor nodes, alias nodes, and merge keys. You can define common settings once and reuse them across the configuration.

profiles:
  ks: &ks
    command: kustomize
    args: [build, .]
    source: >-
      files.filter(f, pathExt(f) in [".yaml", ".yml"])
    hooks:
      postRender:
        - &kubeconform
          command: kubeconform
          args: [-strict, -summary]

  ks-helm:
    <<: *ks
    args: [build, ., --enable-helm]

  helm:
    command: helm
    args: [template, ., --generate-name]
    source: >-
      files.filter(f, pathExt(f) in [".yaml", ".yml", ".tpl"])
    envFrom:
      - callerRef:
          pattern: "^HELM_.+"
    hooks:
      postRender:
        - *kubeconform

❀️ Thanks to goccy/go-yaml.

πŸ“– Examples

Default config - By default, kat includes a configuration that supports helm, kustomize, and generic YAML files. This is a great starting point for writing your own custom config:

Support for custom tools - You can add support for other languages/tools like kcl, jsonnet, flux-local, cue, and so on:

rules:
  - match: >-
      files.exists(f, pathExt(f) == ".k")
    profile: kcl
profiles:
  kcl:
    command: kcl
    args: [run, .]
    source: >-
      files.filter(f, pathExt(f) == ".k")
    envFrom:
      - callerRef:
          pattern: "^KCL_.+"

Content-based detection - Match based on file content, not just names:

rules:
  - # Match Helm v3 specifically
    match: >-
      files.exists(f,
        pathBase(f) == "Chart.yaml" &&
        yamlPath(f, "$.apiVersion") == "v2")
    profile: helm-v3
  - # Match Kubernetes native manifests with specific API versions
    match: >-
      files.exists(f,
        pathExt(f) in [".yaml", ".yml"] &&
        yamlPath(f, "$.apiVersion") in ["apps/v1", "v1"])
    profile: yaml

Using Task - If you use task, you can use your tasks in the kat config:

rules:
  - match: >-
      files.exists(f, pathBase(f) in ["Taskfile.yml", "Taskfile.yaml"])
    profile: task
profiles:
  task:
    command: task
    args: [render]
    source: >-
      files.filter(f, pathExt(f) in [".yaml", ".yml"])
    hooks:
      postRender:
        - command: task
          args: [validate]

Note that you should write your task to:

  • Output the rendered manifests to stdout, and anything else to stderr.
  • Tolerate being called from any directory in the project.
    • E.g., instead of ./folder, use {{joinPath .ROOT_DIR "folder"}}.
  • Not require any additional arguments to run.
    • You can reference {{.USER_WORKING_DIR}} to obtain the path that the user invoked kat from/with.
    • E.g., vars: { PATH: "{{.PATH | default .USER_WORKING_DIR}}" }

If you are concerned about safety (i.e. accidentally calling a task defined by someone else), you can consider not including a rule for task and only allowing it to be invoked manually via the CLI args, or you could write a more narrow match expression (e.g. f.contains("/my-org/")).

🌈 Themes

Themes

Configure a theme with --ui-theme, KAT_UI_THEME, or via config:

ui:
  theme: "dracula"

You can optionally set different themes for different profiles:

profiles:
  helm:
    ui:
      theme: "dracula"
      # ...
  ks:
    ui:
      theme: "tokyonight-storm"
      # ...

We use Chroma for theming, so you can use any styles from the Chroma Style Gallery.

You can also add your own themes in the config:

ui:
  theme: "my-custom-theme"
  themes:
    my-custom-theme:
      styles:
        background: "#abb2bf bg:#282c34"
        punctuation: "#abb2bf"
        keyword: "#c678dd"
        name: "bold #e06c75"
        comment: "italic #8b949e"
        commentSpecial: "bold italic #8b949e"
        # ...

Chroma uses the same syntax as Pygments. Define ui.themes.[name].styles as a map of Pygments Tokens to Styles. You can then reference any theme in ui.theme (or by using the corresponding flag / env var).

πŸ”οΈ Similar Tools

About

TUI for rendering, validating, and displaying local Kubernetes manifests

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages