Skip to content

Latest commit

 

History

History
415 lines (309 loc) · 6.56 KB

File metadata and controls

415 lines (309 loc) · 6.56 KB

Getting Started with graft

graft is a domain-specific YAML merging tool, designed to make it easy to manage and merge multi-part YAML configurations.

Installation

macOS (Homebrew)

brew install graft

Linux/macOS (Binary)

# Download the latest release
curl -L https://github.com/wayneeseguin/graft/releases/latest/download/graft-$(uname -s | tr '[:upper:]' '[:lower:]')-amd64 -o graft

# Make it executable
chmod +x graft

# Move to PATH
sudo mv graft /usr/local/bin/

From Source

go get github.com/wayneeseguin/graft/cmd/graft

Verify Installation

graft --version

Basic Usage

Your First Merge

Create two YAML files:

base.yml:

name: my-app
version: 1.0.0
server:
  port: 8080
  host: localhost

production.yml:

server:
  port: 443
  host: app.example.com
  ssl: true

Merge them:

graft merge base.yml production.yml

Output:

name: my-app
version: 1.0.0
server:
  port: 443
  host: app.example.com
  ssl: true

Understanding Merge Order

Files are merged left-to-right. Later files override values from earlier files:

graft merge first.yml second.yml third.yml
  • first.yml is the base
  • second.yml overrides/adds to first
  • third.yml overrides/adds to the result

Using Operators

graft's power comes from its operators - special expressions that perform operations during merging.

References with grab

Reference other parts of the document:

# config.yml
domain: example.com
api:
  url: (( concat "https://api." domain ))
web:
  url: (( concat "https://www." domain ))

Result:

domain: example.com
api:
  url: https://api.example.com
web:
  url: https://www.example.com

Concatenation

Build strings from parts:

# Example
environment: production
region: us-east-1
resource_name: (( concat "myapp-" environment "-" region ))

Result:

resource_name: myapp-production-us-east-1

Conditional Values

Use the ternary operator for conditionals:

environment: production
debug_mode: (( environment == "production" ? false : true ))
replicas: (( environment == "production" ? 3 : 1 ))

Working with Arrays

Default Array Merging

Arrays of maps with name fields merge by name:

# base.yml
servers:
  - name: web
    port: 8080
  - name: db
    port: 5432

# override.yml
servers:
  - name: web
    port: 80
    ssl: true

Result:

servers:
  - name: web
    port: 80
    ssl: true
  - name: db
    port: 5432

Array Operators

Control array merging explicitly:

# Append to array
items:
  - (( append ))
  - new-item

# Replace array
items:
  - (( replace ))
  - only-item

# Prepend to array
items:
  - (( prepend ))
  - first-item

Environment Variables

Using Environment Variables

# Direct usage
database_url: (( grab $DATABASE_URL ))

# With defaults
port: (( grab $PORT || 8080 ))

# In paths (environment variable expansion)
config: (( grab settings.$ENVIRONMENT.database ))

Example with Defaults

app:
  name: (( grab $APP_NAME || "my-app" ))
  port: (( grab $PORT || 8080 ))
  debug: (( grab $DEBUG || false ))
  database:
    host: (( grab $DB_HOST || "localhost" ))
    port: (( grab $DB_PORT || 5432 ))

Common Patterns

Configuration Layering

Structure your configurations in layers:

config/
  base.yml           # Shared configuration
  development.yml    # Development overrides
  staging.yml        # Staging overrides  
  production.yml     # Production overrides

Usage:

# Development
graft merge config/base.yml config/development.yml

# Production
graft merge config/base.yml config/production.yml

Pruning Temporary Data

Remove temporary scaffolding:

# base.yml
meta:
  environment: production
  region: us-east-1

app:
  name: myapp
  full_name: (( concat meta.environment "-" app.name "-" meta.region ))
graft merge base.yml --prune meta

Using Parameters

Require values to be provided:

# base.yml
database:
  host: (( param "Please provide database.host" ))
  port: 5432
  username: (( param "Please provide database.username" ))
  password: (( param "Please provide database.password" ))

Advanced Features

Multi-Document YAML

Process files with multiple documents:

---
name: doc1
---
name: doc2
graft merge --multi-doc multi.yml

Cherry-Picking

Extract specific parts:

graft merge large-manifest.yml --cherry-pick jobs.web

Vault Integration

Pull secrets from HashiCorp Vault:

database:
  password: (( vault "secret/db:password" ))
api:
  key: (( vault "secret/api:key" ))

Best Practices

1. Organize Your Files

manifests/
  base/
    app.yml
    database.yml
    networking.yml
  environments/
    dev.yml
    staging.yml
    prod.yml

2. Use Meaningful Names

# Good
database_connection_timeout: 30
api_rate_limit: 1000

# Less clear
db_timeout: 30
limit: 1000

3. Document Your Operators

# Calculate replica count based on environment
# Production: 3, Others: 1
replicas: (( environment == "production" ? 3 : 1 ))

4. Validate Early

required:
  api_key: (( param "API_KEY is required" ))
  database_url: (( param "DATABASE_URL is required" ))

5. Keep Secrets Secure

# Don't do this
password: supersecret

# Do this
password: (( vault "secret/app:password" ))
# Or
password: (( grab $PASSWORD ))

Troubleshooting

Debug Mode

See what graft is doing:

graft merge --debug base.yml override.yml

Common Issues

Missing references:

# Error: $.does.not.exist could not be found
value: (( grab does.not.exist ))

# Fix: Provide a default
value: (( grab does.not.exist || "default" ))

Circular references:

# Error: Circular reference detected
a: (( grab b ))
b: (( grab a ))

Type mismatches:

# Error: Cannot concatenate non-string values
result: (( concat "prefix" 123 ))  # 123 is not a string

# Fix: Convert to string first
result: (( concat "prefix" (stringify 123) ))

Next Steps

Getting Help