DEV Community

poolion
poolion

Posted on

Config-Linter: Python CLI Tool for JSON/YAML Config Validation Without External Dependencies

Config Linter: JSON/YAML Config Validation in Pure Python

Configuration files are deceptively fragile. A trailing comma, an undefined variable, or an empty nested object can break your deployment before you even deploy the app. The Config Linter is a lightweight CLI tool written in pure Python that checks for these common issues without requiring any external dependencies.

What Problem Does It Solve?

When managing configuration files for applications, CI/CD pipelines, or infrastructure as code, developers frequently encounter:

  • Trailing commas — syntax errors that break YAML parsers but slip through simple JSON validators
  • Undefined variables like ${DATABASE_HOST} referenced in configs but never set in environment
  • Empty sections in nested config structures that cause silent failures
  • Tedious manual inspection of large configuration files before deployment

Config Linter provides automated validation for these problems.

Installation

# Clone and use directly
git clone https://github.com/Poolion/config-linter.git
cd config-linter
chmod +x config-lint.py
python config-lint.py your-config.yaml

# Add to PATH (Linux/macOS)
cp config-lint.py /usr/local/bin/
Enter fullscreen mode Exit fullscreen mode

Now you can run it from anywhere:

config-lint.py app.yaml --list-keys
Enter fullscreen mode Exit fullscreen mode

Usage Examples

Basic Validation

python config-lint.py database.json
Enter fullscreen mode Exit fullscreen mode

Output:

[OK] Loaded as JSON

Keys found (8):
  - database.host
  - database.port
  - database.username
  - database.password
[...]

===============================================================
Config validates successfully - no issues found!
Enter fullscreen mode Exit fullscreen mode

With Undefined Variable Warnings

python config-lint.py myapp.yaml
Enter fullscreen mode Exit fullscreen mode

Output includes warnings:

[OK] Loaded as JSON

- WARN: Possible undefined variable: ${SOME_VAR}
[*] Trailing comma before }} at position 145
Enter fullscreen mode Exit fullscreen mode

List All Configuration Keys

python config-lint.py config.yaml --list-keys
# Shows every key path in the configuration
Keys found (24):
  - server.host
  - server.port
  - server.debug
  - database.primary
[...]
Enter fullscreen mode Exit fullscreen mode

Reading from Stdin for Pipelines

echo "name: myapp
version: 1.0" | python config-lint.py -
[OK] Loaded as INI/Text/Key-Value
- INFO: Empty configuration object
Enter fullscreen mode Exit fullscreen mode

How It Works

The tool handles multiple configuration formats with a single parser approach:

Detection Order

  1. JSON/YAML detection — Files ending in .json, .yml, or .yaml containing valid JSON syntax are parsed as JSON. Most YAML configs without special anchors/aliases are actually valid JSON anyway.
  2. Key=Value parsing — Treated as INI-style with sections [section] and key = value pairs
  3. Type conversion — Attempts to convert string values to proper types (integers, floats, booleans)

Validation Categories

Check Type What It Detects Example
Undefined variables ${VAR} or %KEY% patterns ${MISSING_VAR} with no definition
Trailing commas Commas before ] or } "a": 1, followed by } breaks YAML
Empty sections Nested objects with zero keys { "empty_section": {} }

Command Line Interface

python config-lint.py [options] <config_file>

Options:
  -c CONFIG   Path to configuration file (or "-" for stdin)
  -l, --list-keys   List all keys found in the configuration
  -h, --help        Show help message and examples
Enter fullscreen mode Exit fullscreen mode

Why Pure Python?

This tool requires no external dependencies—only what comes bundled with Python:

  • argparse — Command-line argument parsing
  • json — JSON file loading
  • re — Regular expression pattern matching

No pyyaml, no ruamel.yaml, no schema validators. Just standard library, making it:

  • Lightweight — No virtual environment needed
  • Portable — Works on any Python installation
  • Script-friendly — Easy to import into other Python tools

Configuration Formats Supported

JSON Example

{
  "server": {
    "host": "${HOST}",  
    "port": 8080,   // Valid - port is a number
    "debug": true
  }
}
Enter fullscreen mode Exit fullscreen mode

YAML Example (YAML-like syntax)

[server]
host: ${HOME_DIR}
port: 3000

[database]
name: myapp_db
# This triggers undefined variable warning for %DB%
connection_url: postgres://%DB%/myapp
Enter fullscreen mode Exit fullscreen mode

INI-style Example

[general]
version = 1.2.0
debug_mode = true

[paths]
logs_dir = /var/log/app
temp_dir = ${TEMP_DIR}
Enter fullscreen mode Exit fullscreen mode

When to Use This Tool

Ideal scenarios for config-lint:

  • CI/CD pipelines — Validate configs before deployment
  • Configuration templates — Ensure example configs have no undefined variables
  • DevOps workflows — Lint Kubernetes manifests alongside other files
  • Documentation — Validate example configuration files in docs
  • Automated testing — Include config validation in test suites

Integration Examples

GitHub Actions Workflow

# .github/workflows/lint-config.yml
name: Validate Configuration Files

on:
  pull_request:
    paths:
      - 'config/**'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: python config-lint.py app.yaml --list-keys
Enter fullscreen mode Exit fullscreen mode

Dockerfile Integration

# Validate configs at build time
RUN python config-lint.py /etc/app/config.yaml || exit 1

COPY config/ /etc/app/conf.d/
CMD ["python-config", "config-lint.py" "/etc/app/conf.d/*.yaml"]
Enter fullscreen mode Exit fullscreen mode

Limitations to Know

As a lightweight tool, Config Linter doesn't attempt to be comprehensive:

  • No schema validation — Cannot check that values match expected types or ranges (use JSON Schema or similar for this)
  • Limited YAML parsing — True YAML with anchors, aliases, or complex multi-line strings requires PyYAML. This parses key=value style content.
  • Shallow nested checks — Recursively checks only 3 levels deep (enough for most configs)
  • No semantic analysis — Cannot detect if a value makes sense in context

These are intentional trade-offs for simplicity and zero-dependency requirements.

Extending the Tool

The code is intentionally simple to extend:

# Add a new check after find_empty()
def check_duplicates(config):
    """Find duplicate keys in nested dicts."""
    issues = []

    def traverse(d, path=''):
        seen = {}
        for k, v in d.items():
            key_id = f"{path}/{k}" if path else str(k)
            if isinstance(v, dict):
                traverse(v, key_id)
            elif v in seen:
                issues.append(f"Duplicate value at {key_id}: {seen[v]} != {key}")
            else:
                seen[v] = key_id

    traverse(config)
    return issues

# Add to main() validation loop
if not isinstance(config, dict):
    ...  # existing checks
else:
    duplicates = check_duplicates(config)
    if duplicates:
        for d in duplicates:
            print(f'- WARN: Duplicate configuration: {d}')
Enter fullscreen mode Exit fullscreen mode

Conclusion

Config Linter fills a practical gap in developer tooling. It's not trying to replace comprehensive validation suites, but provides targeted checks for the most common configuration file mistakes that break deployments and cause confusion.

For minimal overhead—zero dependencies plus about 100 lines of code—it catches issues that manual inspection often misses. Add it to your toolkit when working with configuration-heavy projects.

Project: https://github.com/Poolion/config-linter

If you find this useful, you can support development: https://www.buymeacoffee.com/poolion

Top comments (0)