Migrate from v1beta2 to v1beta3
Overview
This guide walks through converting v1beta2 Preflight specs to v1beta3. The v1beta3 format introduces templating and values-driven configuration, making specs more flexible and maintainable.
Why Migrate?
v1beta3 offers several advantages:
- Reusable specs - One spec works across multiple environments with different values files
 - Dynamic configuration - Toggle checks on/off without editing YAML
 - Self-documenting - Extract requirements documentation automatically
 - Type-safe values - Centralized values with clear structure
 - Reduced duplication - Template repeated patterns instead of copy-paste
 - Runtime context - Messages show actual vs. required values dynamically
 
v1beta2 remains supported - This is not a breaking change. Migrate when the benefits match your needs.
Key Differences
| Feature | v1beta2 | v1beta3 | 
|---|---|---|
| API Version | troubleshoot.sh/v1beta2 | troubleshoot.sh/v1beta3 | 
| Templating | Not supported | Go templates + Sprig | 
| Values | Hardcoded in spec | External values files | 
| Documentation | Comments only | Extractable docString | 
| Configuration | Edit YAML | Supply different values | 
| Toggles | Maintain multiple files | Conditional blocks | 
Migration Process
Migration Checklist
-  Update 
apiVersiontotroubleshoot.sh/v1beta3 -  Add 
docStringto every analyzer - Extract hardcoded values to a values file
 -  Replace hardcoded values with 
{{ .Values.* }}expressions -  Wrap optional checks in 
{{- if .Values.feature.enabled }} - Update messages to use runtime expressions where helpful
 -  Test rendering: 
preflight template spec.yaml --values values.yaml - Test with multiple values scenarios (dev, prod, minimal)
 -  Extract docs: 
preflight docs spec.yaml --values values.yaml - Verify extracted documentation is clear and complete
 
Step 1: Change API Version
# v1beta2
apiVersion: troubleshoot.sh/v1beta2
# v1beta3
apiVersion: troubleshoot.sh/v1beta3
Step 2: Add docStrings
Add documentation to each analyzer using docString. This should describe the requirement, rationale, and links.
Before (v1beta2):
spec:
  analyzers:
    - clusterVersion:
        checkName: Kubernetes version
        outcomes:
          - fail:
              when: "< 1.24.0"
              message: Requires Kubernetes 1.24.0 or later
          - pass:
              message: Kubernetes version is supported
After (v1beta3):
spec:
  analyzers:
    - docString: |
        Title: Kubernetes Version Requirements
        Requirement:
          - Minimum: 1.24.0
          - Recommended: 1.27.0
        Ensures required APIs and security patches are available.
        Older versions may lack necessary features or contain known vulnerabilities.
        Links:
          - https://kubernetes.io/releases/
      clusterVersion:
        checkName: Kubernetes version
        outcomes:
          - fail:
              when: "< 1.24.0"
              message: Requires Kubernetes 1.24.0 or later
          - pass:
              message: Kubernetes version is supported
Step 3: Extract Values
Identify hardcoded values and move them to a values file.
Before (v1beta2):
spec:
  analyzers:
    - clusterVersion:
        outcomes:
          - fail:
              when: "< 1.24.0"
              message: Requires Kubernetes 1.24.0 or later
          - warn:
              when: "< 1.27.0"
              message: Kubernetes 1.27.0 or later recommended
    - storageClass:
        storageClassName: "standard"
        outcomes:
          - fail:
              message: StorageClass 'standard' not found
    - nodeResources:
        outcomes:
          - fail:
              when: "count() < 3"
              message: Requires at least 3 nodes
Create values.yaml:
kubernetes:
  enabled: true
  minVersion: "1.24.0"
  recommendedVersion: "1.27.0"
storage:
  enabled: true
  className: "standard"
nodes:
  enabled: true
  minimum: 3
After (v1beta3):
spec:
  analyzers:
    {{- if .Values.kubernetes.enabled }}
    - docString: |
        Title: Kubernetes Version
        Requirement:
          - Minimum: {{ .Values.kubernetes.minVersion }}
          - Recommended: {{ .Values.kubernetes.recommendedVersion }}
        Ensures compatibility with required APIs.
      clusterVersion:
        checkName: Kubernetes version
        outcomes:
          - fail:
              when: '< {{ .Values.kubernetes.minVersion }}'
              message: Requires Kubernetes {{ .Values.kubernetes.minVersion }} or later
          - warn:
              when: '< {{ .Values.kubernetes.recommendedVersion }}'
              message: Kubernetes {{ .Values.kubernetes.recommendedVersion }} or later recommended
          - pass:
              message: Kubernetes version meets requirements
    {{- end }}
    {{- if .Values.storage.enabled }}
    - docString: |
        Title: Storage Class
        Requirement:
          - StorageClass "{{ .Values.storage.className }}" must exist
        Required for dynamic volume provisioning.
      storageClass:
        checkName: Storage Class
        storageClassName: '{{ .Values.storage.className }}'
        outcomes:
          - fail:
              message: StorageClass {{ .Values.storage.className }} not found
          - pass:
              message: StorageClass {{ .Values.storage.className }} exists
    {{- end }}
    {{- if .Values.nodes.enabled }}
    - docString: |
        Title: Node Count
        Requirement:
          - Minimum {{ .Values.nodes.minimum }} nodes
        Ensures high availability and capacity.
      nodeResources:
        checkName: Node count
        outcomes:
          - fail:
              when: 'count() < {{ .Values.nodes.minimum }}'
              message: Requires at least {{ .Values.nodes.minimum }} nodes
          - pass:
              message: Sufficient nodes available
    {{- end }}
Step 4: Add Conditional Toggles
Wrap optional analyzers in conditionals so they can be enabled/disabled via values.
This is especially useful for:
- Database checks (not all deployments use databases)
 - Distribution-specific checks
 - Development vs. production requirements
 - Custom resource definitions that may not always be needed
 
Pattern:
{{- if .Values.feature.enabled }}
- docString: |
    ...
  analyzerType:
    ...
{{- end }}
Complete Example: Side-by-Side
v1beta2 Original
apiVersion: troubleshoot.sh/v1beta2
kind: Preflight
metadata:
  name: my-app-preflight
spec:
  collectors:
    - clusterResources: {}
  analyzers:
    # Check Kubernetes version
    - clusterVersion:
        outcomes:
          - fail:
              when: "< 1.24.0"
              message: Kubernetes 1.24.0 or later required
          - warn:
              when: "< 1.27.0"
              message: Kubernetes 1.27.0 recommended
          - pass:
              message: Kubernetes version OK
    # Check node count
    - nodeResources:
        outcomes:
          - fail:
              when: "count() < 3"
              message: At least 3 nodes required for HA
          - pass:
              message: Sufficient nodes
    # Check memory per node
    - nodeResources:
        outcomes:
          - fail:
              when: "min(memoryCapacity) < 16Gi"
              message: Each node must have at least 16 GiB memory
          - pass:
              message: Memory requirements met
    # Check storage class
    - storageClass:
        storageClassName: "standard"
        outcomes:
          - fail:
              message: StorageClass 'standard' not found
          - pass:
              message: StorageClass exists
    # Check distribution
    - distribution:
        outcomes:
          - fail:
              when: "== kind"
              message: kind is not supported in production
          - pass:
              when: "== eks"
              message: EKS is supported
          - pass:
              when: "== gke"
              message: GKE is supported
          - pass:
              when: "== aks"
              message: AKS is supported
          - warn:
              message: Unknown distribution
v1beta3 Converted
preflight-v1beta3.yaml:
apiVersion: troubleshoot.sh/v1beta3
kind: Preflight
metadata:
  name: my-app-preflight
spec:
  collectors:
    - clusterResources: {}
  analyzers:
    {{- if .Values.kubernetes.enabled }}
    - docString: |
        Title: Kubernetes Version Requirements
        Requirement:
          - Minimum: {{ .Values.kubernetes.minVersion }}
          - Recommended: {{ .Values.kubernetes.recommendedVersion }}
        Ensures the cluster has required APIs and security patches.
        Older versions may not support features required by this application.
        Links:
          - https://kubernetes.io/releases/
      clusterVersion:
        checkName: Kubernetes version
        outcomes:
          - fail:
              when: '< {{ .Values.kubernetes.minVersion }}'
              message: Kubernetes {{ .Values.kubernetes.minVersion }} or later required
          - warn:
              when: '< {{ .Values.kubernetes.recommendedVersion }}'
              message: Kubernetes {{ .Values.kubernetes.recommendedVersion }} recommended for optimal performance
          - pass:
              message: Kubernetes version meets all requirements
    {{- end }}
    {{- if .Values.nodes.enabled }}
    - docString: |
        Title: High Availability Node Count
        Requirement:
          - Minimum {{ .Values.nodes.minimum }} nodes
        Multiple nodes ensure the application remains available during
        node maintenance or failures. Single-node clusters risk downtime.
      nodeResources:
        checkName: Node count
        outcomes:
          - fail:
              when: 'count() < {{ .Values.nodes.minimum }}'
              message: At least {{ .Values.nodes.minimum }} nodes required for high availability
          - pass:
              message: Sufficient nodes for HA ({{ "{{" }} count() {{ "}}" }} nodes)
    {{- end }}
    {{- if .Values.memory.enabled }}
    - docString: |
        Title: Per-Node Memory Requirements
        Requirement:
          - Each node: minimum {{ .Values.memory.minGi }} GiB
        Application pods require {{ .Values.memory.minGi }} GiB to run database
        and cache workloads. Insufficient memory causes scheduling failures.
      nodeResources:
        checkName: Node memory
        outcomes:
          - fail:
              when: 'min(memoryCapacity) < {{ .Values.memory.minGi }}Gi'
              message: Each node must have at least {{ .Values.memory.minGi }} GiB memory
          - pass:
              message: Memory requirements met (smallest node: {{ "{{" }} min(memoryCapacity) {{ "}}" }})
    {{- end }}
    {{- if .Values.storage.enabled }}
    - docString: |
        Title: Storage Class Availability
        Requirement:
          - StorageClass "{{ .Values.storage.className }}" must exist
        Dynamic volume provisioning depends on a configured StorageClass.
        Without it, PVCs cannot be automatically fulfilled.
      storageClass:
        checkName: Storage Class
        storageClassName: '{{ .Values.storage.className }}'
        outcomes:
          - fail:
              message: StorageClass '{{ .Values.storage.className }}' not found
          - pass:
              message: StorageClass '{{ .Values.storage.className }}' exists
    {{- end }}
    {{- if .Values.distribution.enabled }}
    - docString: |
        Title: Supported Kubernetes Distribution
        Requirement:
          - Must be one of: {{ join ", " .Values.distribution.supported }}
          - Must NOT be: {{ join ", " .Values.distribution.unsupported }}
        This application is tested and certified on specific distributions.
        Unsupported distributions may have compatibility issues.
      distribution:
        checkName: Distribution check
        outcomes:
          {{- range $dist := .Values.distribution.unsupported }}
          - fail:
              when: '== {{ $dist }}'
              message: '{{ $dist }} is not supported in production'
          {{- end }}
          {{- range $dist := .Values.distribution.supported }}
          - pass:
              when: '== {{ $dist }}'
              message: '{{ $dist }} is a supported distribution'
          {{- end }}
          - warn:
              message: Unable to determine distribution. Supported: {{ join ", " .Values.distribution.supported }}
    {{- end }}
values.yaml:
kubernetes:
  enabled: true
  minVersion: "1.24.0"
  recommendedVersion: "1.27.0"
nodes:
  enabled: true
  minimum: 3
memory:
  enabled: true
  minGi: 16
storage:
  enabled: true
  className: "standard"
distribution:
  enabled: true
  supported:
    - eks
    - gke
    - aks
  unsupported:
    - kind
Usage:
# Render template
preflight template preflight-v1beta3.yaml --values values.yaml
# Run checks
preflight preflight-v1beta3.yaml --values values.yaml
# Extract documentation
preflight docs preflight-v1beta3.yaml --values values.yaml -o REQUIREMENTS.md
Common Migration Patterns
Pattern 1: Multiple Environment Configurations
Before (v1beta2): Maintain separate files per environment.
preflight-dev.yaml
preflight-staging.yaml
preflight-prod.yaml
After (v1beta3): One spec, multiple values files.
# preflight.yaml (shared)
apiVersion: troubleshoot.sh/v1beta3
kind: Preflight
spec:
  analyzers:
    {{- if .Values.nodes.enabled }}
    - nodeResources:
        outcomes:
          - fail:
              when: 'count() < {{ .Values.nodes.minimum }}'
    {{- end }}
# values-dev.yaml
nodes:
  enabled: false  # Dev can be single-node
# values-prod.yaml
nodes:
  enabled: true
  minimum: 3  # Prod requires HA
# Dev
preflight preflight.yaml --values values-dev.yaml
# Prod
preflight preflight.yaml --values values-prod.yaml
Pattern 2: Optional Database Checks
Before (v1beta2): Comment out or manually remove.
# Uncomment if using PostgreSQL
# - postgres:
#     uri: "postgres://..."
After (v1beta3): Toggle in values.
spec:
  collectors:
    {{- if .Values.databases.postgres.enabled }}
    - postgres:
        collectorName: postgres
        uri: '{{ .Values.databases.postgres.uri }}'
    {{- end }}
  analyzers:
    {{- if .Values.databases.postgres.enabled }}
    - docString: |
        Title: PostgreSQL Connectivity
        Requirement:
          - Database must be reachable at {{ .Values.databases.postgres.uri }}
        Application requires PostgreSQL for persistent storage.
      postgres:
        collectorName: postgres
        outcomes:
          - fail:
              message: Cannot connect to PostgreSQL
          - pass:
              message: PostgreSQL connection successful
    {{- end }}
# values.yaml
databases:
  postgres:
    enabled: true
    uri: "postgres://user:pass@postgres:5432/db?sslmode=disable"
Pattern 3: Dynamic Outcome Messages
Before (v1beta2): Static messages.
- nodeResources:
    outcomes:
      - fail:
          when: "sum(cpuCapacity) < 8"
          message: "Need at least 8 CPU cores"
After (v1beta3): Show actual gap.
- nodeResources:
    outcomes:
      - fail:
          when: 'sum(cpuCapacity) < {{ .Values.cpu.minimum }}'
          message: |
            Insufficient CPU capacity.
            Required: {{ .Values.cpu.minimum }} cores
            Available: {{ "{{" }} sum(cpuCapacity) {{ "}}" }} cores
            Need {{ "{{" }} subtract({{ .Values.cpu.minimum }}, sum(cpuCapacity)) {{ "}}" }} more cores
Tips and Best Practices
- Start with values extraction - Identify all hardcoded values first
 - Group related values - Use nested structure (e.g., 
nodes.count,nodes.memory) - Always include 
enabledflags - Allows disabling entire check groups - Keep docStrings simple - Use templates minimally in docs, primarily for values
 - Test incrementally - Migrate one analyzer at a time, test between changes
 - Use consistent naming - Match values keys to analyzer types where possible
 - Provide defaults - Create a "base" values file with sensible defaults
 - Document values schema - Add comments in values files explaining each field
 
Troubleshooting
Template Syntax Errors
Error: template: ...:10:5: executing "..." at <.Values.foo>: map has no entry for key "foo"
Fix: Ensure the value exists in your values file. Check spelling and nesting.
# Missing: foo
bar: value
# Fixed:
foo: value
bar: value
Quoting Issues
Error: YAML parsing errors with template expressions.
Fix: Always quote template expressions in YAML string contexts:
# Wrong
storageClassName: {{ .Values.storage.className }}
# Correct
storageClassName: '{{ .Values.storage.className }}'
Conditional Not Working
Error: Check still runs even though enabled: false
Fix: Ensure conditional syntax is correct:
# Wrong - note the spacing
{{ if .Values.feature.enabled }}
# Correct - note the dash to strip whitespace
{{- if .Values.feature.enabled }}
Runtime vs. Template Expressions
Confusion: When to use {{ }} vs {{ "{{" }} {{ "}}" }}?
Template-time (single braces): Values from values files, rendered before execution
when: '< {{ .Values.kubernetes.minVersion }}'
Runtime (escaped braces): Collector data, evaluated during preflight execution
message: 'Found {{ "{{" }} count() {{ "}}" }} nodes'
Additional Resources
- v1beta3 Overview - Complete guide to v1beta3 features
 - Authoring Guide - Detailed reference for all analyzer types
 - Analyze Reference - All available analyzers
 - Collect Reference - All available collectors
 
Need Help?
- Open an issue: GitHub Issues
 - Ask in discussions: GitHub Discussions