This document describes the enhanced DSL features added to Comet to reduce boilerplate and improve developer experience.
💡 Important: Comet's DSL is a superset of JavaScript. The features below are built-in conveniences, but you can create any helper functions you want for your team's specific patterns. See Userland Patterns below.
Setting multiple environment variables required repetitive function calls:
envs('DIGITALOCEAN_TOKEN', secrets('sops://secrets.enc.yaml#/digitalocean/token'))
envs('AWS_ACCESS_KEY_ID', secrets('sops://secrets.enc.yaml#/digitalocean/spaces_access_key'))
envs('AWS_SECRET_ACCESS_KEY', secrets('sops://secrets.enc.yaml#/digitalocean/spaces_secret_key'))
envs('CLOUDFLARE_API_TOKEN', secrets('sops://secrets.enc.yaml#/cloudflare/api_token'))The envs() function now accepts an object/map to set multiple variables at once:
envs({
DIGITALOCEAN_TOKEN: secrets('sops://secrets.enc.yaml#/digitalocean/token'),
AWS_ACCESS_KEY_ID: secrets('sops://secrets.enc.yaml#/digitalocean/spaces_access_key'),
AWS_SECRET_ACCESS_KEY: secrets('sops://secrets.enc.yaml#/digitalocean/spaces_secret_key'),
CLOUDFLARE_API_TOKEN: secrets('sops://secrets.enc.yaml#/cloudflare/api_token')
})The original syntax still works:
// Single key-value pair
envs('MY_VAR', 'my_value')
// Get environment variable
const token = envs('MY_VAR')- 40% less code - 4 function calls reduced to 1
- Better grouping - Related credentials are visually grouped
- More readable - Natural JavaScript object syntax
Every secret reference required verbose, repetitive syntax:
api_key: secrets('sops://secrets.enc.yaml#/datadog/api_key'),
app_key: secrets('sops://secrets.enc.yaml#/datadog/app_key'),
slack_token: secrets('sops://secrets.enc.yaml#/argocd/slack_token')New secret() shorthand function with configurable defaults:
secretsConfig({
defaultProvider: 'sops', // Default: 'sops'
defaultPath: 'secrets.enc.yaml' // Default: 'secrets.enc.yaml'
})api_key: secret('datadog/api_key'),
app_key: secret('datadog/app_key'),
slack_token: secret('argocd/slack_token')api_key: secret('datadog.api_key'),
app_key: secret('datadog.app_key'),
slack_token: secret('argocd.slack_token')Both notations are equivalent - dots are automatically converted to slashes.
If you need to use a different provider or path for a specific secret, use the full syntax:
// Full syntax still works
token: secrets('sops://other-secrets.yaml#/special/token')
// Or from 1Password
password: secrets('op://vault/item/field')The original secrets() function remains unchanged and fully functional.
- 50% reduction in characters - Much more concise
- Better readability - Less visual noise
- Easier refactoring - Change default path in one place
- Dot notation - More natural for hierarchical secrets
// Configure once per stack
secretsConfig({
defaultProvider: 'sops',
defaultPath: 'secrets.enc.yaml'
})
// Database component
component('database', 'modules/postgres', {
// OLD: secrets('sops://secrets.enc.yaml#/database/admin_password')
admin_password: secret('database/admin_password'),
// OLD: secrets('sops://secrets.enc.yaml#/database/replication_password')
replication_password: secret('database.replication_password'),
// Full syntax when needed
backup_key: secrets('sops://backup-secrets.yaml#/db/backup_key')
})Instead of building every pattern into Comet, you can create your own helper functions in JavaScript. This keeps Comet minimal while giving you maximum flexibility.
Why not build-in domain helpers, component groups, tag templates, etc.?
- ❌ Too opinionated - not everyone uses the same patterns
- ❌ Creates maintenance burden - supporting edge cases
- ✅ JavaScript is powerful enough - you can build what you need
- ✅ Transparent - you see exactly what your helpers do
- ✅ Flexible - easy to modify for your specific needs
Your own domain helpers:
const opts = { base_domain: 'example.io' }
// Define your team's pattern
function subdomain(name) {
return `${name}.{{ .stack }}.${opts.base_domain}`
}
function fqdn(name) {
return `${name}.${opts.base_domain}`
}
// Use them
component('admin', 'modules/app', {
domain_name: subdomain('admin') // admin.dev.example.io
})
component('api', 'modules/api', {
domain_name: fqdn('api') // api.example.io
})Component factories:
function k8sApp(name, config) {
return component(name, 'modules/k8s-app', {
namespace: 'default',
replicas: 2,
...config
})
}
const api = k8sApp('api', {
replicas: 3,
database_url: db.connection_string
})Credential presets:
function setupDigitalOcean() {
envs({
DIGITALOCEAN_TOKEN: secret('digitalocean/token'),
AWS_ACCESS_KEY_ID: secret('digitalocean/spaces_access_key'),
AWS_SECRET_ACCESS_KEY: secret('digitalocean/spaces_secret_key')
})
}
setupDigitalOcean()See Userland Patterns for comprehensive examples and best practices.
Putting it all together:
const settings = {
org: 'mycompany',
common_name: 'platform',
domain_name: 'mycompany.io'
}
stack('staging', { settings })
backend('gcs', {
bucket: 'terraform-state',
prefix: `${settings.org}/{{ .stack }}/{{ .component }}`
})
// Configure secrets defaults
secretsConfig({
defaultProvider: 'sops',
defaultPath: 'secrets.enc.yaml'
})
// Bulk environment variables
envs({
DIGITALOCEAN_TOKEN: secret('digitalocean/token'),
AWS_ACCESS_KEY_ID: secret('aws/access_key'),
AWS_SECRET_ACCESS_KEY: secret('aws/secret_key'),
CLOUDFLARE_API_TOKEN: secret('cloudflare/api_token')
})
// Database with shorthand secrets
const database = component('database', 'modules/postgres', {
admin_password: secret('database.admin_password'),
admin_ui_domain: subdomain('pgweb'),
replicas: 3
})
// Monitoring stack
component('monitoring', 'modules/monitoring', {
slack_webhook: secret('monitoring/slack_webhook'),
grafana_domain: subdomain('grafana'),
prometheus_domain: subdomain('prometheus'),
database_url: database.connection_string
})
// Public API (no stack in domain)
component('api', 'modules/api', {
api_key: secret('api.key'),
domain_name: fqdn('api'),
database_url: database.connection_string
})Result: ~40% less code with the same functionality!
Before:
envs('TOKEN', secrets('sops://secrets.enc.yaml#/my/token'))
envs('KEY', secrets('sops://secrets.enc.yaml#/my/key'))
component('app', 'modules/app', {
password: secrets('sops://secrets.enc.yaml#/app/password'),
domain_name: 'app.{{ .stack }}.{{ .settings.domain_name }}'
})After:
const opts = { base_domain: 'example.io' }
secretsConfig({
defaultProvider: 'sops',
defaultPath: 'secrets.enc.yaml'
})
envs({
TOKEN: secret('my/token'),
KEY: secret('my/key')
})
// Create your own domain helper if you want it
const subdomain = (name) => `${name}.{{ .stack }}.${opts.base_domain}`
component('app', 'modules/app', {
password: secret('app/password'),
domain_name: subdomain('app')
})All new features are backward compatible. You can:
- Keep existing code as-is
- Use new features in new components
- Gradually refactor existing components
No breaking changes!
| Feature | Code Reduction | Key Benefit |
|---|---|---|
| Bulk Environment Variables | ~75% | Better grouping of related config |
| Secrets Path Shorthand | ~50% | Less visual noise, easier refactoring |
| Userland Patterns | N/A | Maximum flexibility, no opinions |
Overall: ~30-40% less boilerplate while maintaining clarity and flexibility.