Skip to content

A Helm plugin that uses Common Expression Language (CEL) to validate values. Instead of using JSON Schema in values.schema.json, you can write more expressive validation rules using CEL in values.cel.yaml.

License

Notifications You must be signed in to change notification settings

idsulik/helm-cel

Repository files navigation

License Current Release GitHub Repo stars GitHub all releases GitHub issues GitHub pull requests codecov Artifact Hub

Helm CEL Plugin

A Helm plugin that uses Common Expression Language (CEL) to validate values. Instead of using JSON Schema in values.schema.json, you can write more expressive validation rules using CEL in values.cel.yaml.

Installation

helm plugin install https://github.com/idsulik/helm-cel

Usage

Create a values.cel.yaml file in your chart directory alongside your values.yaml file:

rules:
  - expr: "has(values.service) && has(values.service.port)"
    desc: "service port is required"
  
  - expr: "values.service.port >= 1 && values.service.port <= 65535"
    desc: "service port must be between 1 and 65535"
  
  - expr: "!(has(values.replicaCount)) || values.replicaCount >= 1"
    desc: "if replicaCount is set, it must be at least 1"

Then run validation:

helm cel /path/to/your/chart

Example

Given this values.yaml:

service:
  type: ClusterIP
  port: 80

replicaCount: 1

image:
  repository: nginx
  tag: latest

And this values.cel.yaml:

rules:
  - expr: "has(values.service) && has(values.service.port)"
    desc: "service port is required"
  
  - expr: "values.service.port >= 1 && values.service.port <= 65535"
    desc: "service port must be between 1 and 65535"
  
  - expr: "values.replicaCount >= 1"
    desc: "replica count must be at least 1"
  
  - expr: |
      has(values.image) && 
      has(values.image.repository) && 
      has(values.image.tag)
    desc: "image repository and tag are required"

If validation fails, you'll get a clear error message:

❌ Validation failed: replica count must be at least 1
   Rule: values.replicaCount >= 1
   Path: replicaCount
   Current value: 0

Writing Validation Rules

Each rule in values.cel.yaml consists of:

  • expr: A CEL expression that should evaluate to true for valid values
  • desc: A description of what the rule validates
  • severity: Optional severity level ("error" or "warning", defaults to "error")

The values.cel.yaml can also contain named expressions that can be reused across rules:

expressions:
  # Named expressions for reuse
  portRange: 'values.service.port >= 1 && values.service.port <= 65535'
  nodePortRange: 'values.service.nodePort >= 30000 && values.service.nodePort <= 32767'

rules:
  - expr: "${portRange}"
    desc: "Service port must be between 1 and 65535"
  
  - expr: 'values.service.type == "NodePort" ? ${nodePortRange} : true'
    desc: "NodePort must be between 30000 and 32767 when service type is NodePort"
  
  - expr: "values.replicaCount >= 2"
    desc: "Consider running multiple replicas for high availability"
    severity: warning

Severity Levels

Rules can have two severity levels:

  • error: Validation fails if the rule is not satisfied (default)
  • warning: Shows a warning but allows validation to pass

Example output with warnings:

Found 1 warning(s):

⚠️ Service port must be between 1 and 65535 and replica count must be within min and max bounds
   Rule: (values.service.port >= 1 && values.service.port <= 65535)  && (values.replicaCount >= values.minReplicas &&   values.replicaCount <= values.maxReplicas)
   Path: service.port
   Current value: 80801111111
-------------------------------------------------
⚠️✅ Values validation successful with warnings!

Common Validation Patterns

  1. Required fields:
- expr: "has(values.fieldName)"
  desc: "fieldName is required"
  1. Value constraints:
- expr: "values.number >= 0 && values.number <= 100"
  desc: "number must be between 0 and 100"
  1. Conditional requirements:
- expr: "!(has(values.optional)) || values.optional >= 0"
  desc: "if optional is set, it must be non-negative"
  1. Type validation:
- expr: "type(values.ports) == list"
  desc: "ports must be a list"
  1. Complex object validation:
- expr: |
    has(values.container) && 
    has(values.container.image) && 
    has(values.container.tag)
  desc: "container must have image and tag"
  1. Resource validation:
expressions:
  validateResources: '
    has(values.resources) &&
    has(values.resources.requests) &&
    has(values.resources.limits) &&
    matches(string(values.resources.requests.memory), r"^[0-9]+(Mi|Gi)$") &&
    matches(string(values.resources.limits.memory), r"^[0-9]+(Mi|Gi)$") &&
    matches(string(values.resources.requests.cpu), r"^[0-9]+m$|^[0-9]+$") &&
    matches(string(values.resources.limits.cpu), r"^[0-9]+m$|^[0-9]+$")
  '

rules:
  - expr: "${validateResources}"
    desc: "Resource requests and limits must be properly formatted"
  1. Complex service validation:
expressions:
  portRange: 'values.service.port >= 1 && values.service.port <= 65535'

rules:
  - expr: 'values.service.type in ["ClusterIP", "NodePort", "LoadBalancer"]'
    desc: "Service type must be valid"

  - expr: "${portRange}"
    desc: "Service port must be valid"

  - expr: '!values.ingress.enabled || has(values.ingress.className)'
    desc: "Ingress className should be specified when enabled"
    severity: warning
  1. Conditional validation with reusable expressions:
expressions:
  nodePortRange: 'values.service.nodePort >= 30000 && values.service.nodePort <= 32767'

rules:
  - expr: 'values.service.type == "NodePort" ? ${nodePortRange} : true'
    desc: "NodePort must be in valid range when service type is NodePort"

Development

Requirements:

  • Go 1.22 or later

Build:

make build

Install locally:

make install

Run tests:

make test

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

Distributed under the MIT License. See LICENSE for more information.

About

A Helm plugin that uses Common Expression Language (CEL) to validate values. Instead of using JSON Schema in values.schema.json, you can write more expressive validation rules using CEL in values.cel.yaml.

Topics

Resources

License

Stars

Watchers

Forks