Skip to content
GitHub stars

Template Type Conversion

^1.1.15

How to produce correctly-typed integers, floats, and booleans in Lynq templates.

Related pages

The YAML Quoting Problem

When writing LynqForm CRDs in YAML, template expressions must be quoted because the YAML parser requires it:

yaml
# INVALID YAML — parser error
containerPort: {{ .appPort }}

# Valid YAML — must quote template expressions
containerPort: "{{ .appPort }}"

The problem: Quotes make the YAML valid, but the rendered output remains a string, causing Kubernetes API validation to fail for numeric and boolean fields:

yaml
# After rendering without type functions
containerPort: "8080"  # Kubernetes rejects — expected integer
replicas: "3"          # Kubernetes rejects — expected integer
enabled: "true"        # Kubernetes rejects — expected boolean

# What Kubernetes expects
containerPort: 8080    # integer
replicas: 3            # integer
enabled: true          # boolean

Type Conversion Functions

int(value)

Convert to integer:

yaml
replicas: "{{ .maxReplicas | int }}"                   # "3" → 3
containerPort: "{{ .appPort | int }}"                  # "8080" → 8080
replicas: "{{ .replicas | default \"2\" | int }}"      # chain with default
value: "{{ .cpuCount | int }}"                         # 2.8 → 2 (truncates)

Conversion rules:

  • String "123"123
  • Float 2.82 (truncates)
  • Already int → returns as-is
  • Invalid input → 0 (graceful fallback)

float(value)

Convert to float64:

yaml
resources:
  limits:
    cpu: "{{ .cpuLimit | float }}"              # "1.5" → 1.5
targetCPUUtilization: "{{ .threshold | float }}" # "75.5" → 75.5

Conversion rules:

  • String "1.5"1.5
  • Int 22.0
  • Already float → returns as-is
  • Invalid input → 0.0

bool(value)

Convert to boolean:

yaml
enabled: "{{ .featureEnabled | bool }}"                     # "true" → true
readOnly: "{{ .isReadOnly | bool }}"                        # "false" → false
automountServiceAccountToken: "{{ .autoMount | bool }}"    # 1 → true, 0 → false

Truthy valuestrue:

  • Strings: "true", "True", "TRUE", "1", "yes", "Yes", "YES", "y", "Y"
  • Numbers: any non-zero integer (1, 42, -5)
  • Boolean: true

Falsy valuesfalse:

  • Strings: "false", "False", "FALSE", "0", "no", "No", "NO", "n", "N", "" (empty)
  • Numbers: 0
  • Boolean: false

When to Use Type Conversion

Use type conversion for

  • Integers: replicas, containerPort, targetPort, minReplicas, maxReplicas
  • Floats: cpu resource limits/requests, targetCPUUtilizationPercentage
  • Booleans: automountServiceAccountToken, readOnlyRootFilesystem, privileged

Don't use type conversion for

  • Environment variable values — always strings in containers
  • Labels and annotations — always strings
  • Command arguments — always strings
  • ConfigMap/Secret data values — always strings
  • Image tags — always strings even if numeric-looking ("1.2.3")

Complete Example

yaml
apiVersion: operator.lynq.sh/v1
kind: LynqForm
metadata:
  name: typed-app
spec:
  hubId: production-db
  deployments:
    - id: app
      nameTemplate: "{{ .uid }}-api"
      spec:
        apiVersion: apps/v1
        kind: Deployment
        spec:
          replicas: "{{ .maxReplicas | default \"2\" | int }}"
          template:
            spec:
              automountServiceAccountToken: "{{ .mountToken | bool }}"
              containers:
                - name: app
                  image: "{{ .image }}"
                  ports:
                    - containerPort: "{{ .appPort | int }}"
                      protocol: TCP
                  env:
                    # String fields — no conversion needed
                    - name: APP_ENV
                      value: "{{ .environment }}"
                    - name: NODE_ID
                      value: "{{ .uid }}"
                    # Integer env var — keep as string (env values are always strings)
                    - name: MAX_CONNECTIONS
                      value: "{{ .maxConns }}"
                  resources:
                    limits:
                      cpu: "{{ .cpuLimit | float }}"
                      memory: "{{ .memoryLimit }}Mi"
                    requests:
                      cpu: "{{ .cpuRequest | float }}"
                      memory: "{{ .memoryRequest }}Mi"

How It Works

The int/float/bool functions use type markers internally to survive the template rendering boundary:

  1. Template function wraps the result:

    go
    int("42") → "__LYNQ_TYPE_INT__42"  // internal representation
  2. Go template engine processes normally — the marker is treated as a string and survives evaluation.

  3. Controller detects and restores the type:

    go
    renderUnstructured() detects marker → converts to native Go int42
  4. Kubernetes receives a correctly-typed value:

    yaml
    containerPort: 42  # pure integer, no quotes
Why not use Sprig's atoi?

Go's text/template engine always returns rendered results as strings, regardless of what type a function returns internally. Sprig's atoi returns an integer during template execution, but the final output is still a string.

The type marker approach is the only way to preserve type information across the template rendering boundary.

See Also