Skip to main content

Command Palette

Search for a command to run...

Arger - Parameters Across Languages

Solving A Pet Peeve With Google Opal

Updated
6 min read
Arger - Parameters Across Languages

Effective parameter handling is fundamental to building flexible, maintainable automation systems. This guide explores how different languages and tools approach parameter management, using a user account creation scenario as our running example.

The Use Case

We'll examine how to handle user account parameters across different contexts:

  • username: The account identifier

  • address: Physical or mailing address

  • phone: Contact phone number

  • email: Email address

Python: Function Arguments and Environment Variables

Python offers multiple approaches to parameter handling. The most straightforward method uses function arguments with type hints and default values:

# user_manager.py
import sys
import os

def create_user(username: str, email: str, address: str = "", phone: str = "") -> dict:
    """
    Create a user account with the provided parameters.

    Args:
        username: Required username for the account
        email: Required email address
        address: Optional physical address
        phone: Optional phone number

    Returns:
        Dictionary containing user details
    """
    user_data = {
        "username": username,
        "email": email,
        "address": address if address else "Not provided",
        "phone": phone if phone else "Not provided"
    }

    print(f"Creating user account:")
    for key, value in user_data.items():
        print(f"  {key}: {value}")

    return user_data

def main():
    # Method 1: Command-line arguments
    if len(sys.argv) >= 3:
        username = sys.argv[1]
        email = sys.argv[2]
        address = sys.argv[3] if len(sys.argv) > 3 else ""
        phone = sys.argv[4] if len(sys.argv) > 4 else ""
    else:
        # Method 2: Environment variables as fallback
        username = os.getenv("USERNAME", "default_user")
        email = os.getenv("EMAIL", "user@example.com")
        address = os.getenv("ADDRESS", "")
        phone = os.getenv("PHONE", "")

    create_user(username, email, address, phone)

if __name__ == "__main__":
    main()

Python's flexibility allows you to combine multiple parameter sources, with command-line arguments taking precedence over environment variables.

The standard library has argparse, and whole page for choosing an argument parser. There are excellent packages to create CLIs like typer and click.

Bash: Positional Parameters and Named Variables

Bash scripts use positional parameters and can validate their presence before execution:

#!/bin/bash
# create_user.sh

# Function to create user with parameters
create_user() {
    local username=$1
    local email=$2
    local address=${3:-"Not provided"}
    local phone=${4:-"Not provided"}

    echo "Creating user account:"
    echo "  username: $username"
    echo "  email: $email"
    echo "  address: $address"
    echo "  phone: $phone"

    # Simulate user creation
    echo "User account created successfully"
}

# Validate required parameters
if [ $# -lt 2 ]; then
    echo "Error: Missing required parameters"
    echo "Usage: $0 <username> <email> [address] [phone]"
    exit 1
fi

# Extract parameters
USERNAME=$1
EMAIL=$2
ADDRESS=${3:-""}
PHONE=${4:-""}

# Call the function
create_user "$USERNAME" "$EMAIL" "$ADDRESS" "$PHONE"

The ${variable:-default} syntax provides a clean way to specify default values for optional parameters.

Dockerfile: Build Arguments and Environment Variables

Dockerfiles distinguish between build-time arguments (ARG) and runtime environment variables (ENV):

# Dockerfile
FROM python:3.9-slim

# Build-time arguments with defaults
ARG APP_VERSION=1.0.0
ARG PYTHON_ENV=production

# Set environment variables for runtime
ENV USERNAME=""
ENV EMAIL=""
ENV ADDRESS=""
ENV PHONE=""

# Create application directory
WORKDIR /app

# Copy application files
COPY user_manager.py .
COPY create_user.sh .

# Make bash script executable
RUN chmod +x create_user.sh

# Install any required packages
RUN pip install --no-cache-dir --upgrade pip

# Default command
CMD ["python", "user_manager.py"]

You can override these at build time:

docker build --build-arg APP_VERSION=2.0.0 -t user-manager .

And at runtime:

docker run -e USERNAME="jdoe" -e EMAIL="jdoe@example.com" user-manager

Jenkinsfile: Declarative Pipeline Parameters

Jenkins pipelines provide a structured approach to parameter handling through the parameters directive:

pipeline {
    agent any

    parameters {
        string(
            name: 'USERNAME',
            defaultValue: 'testuser',
            description: 'Username for the account',
            trim: true
        )
        string(
            name: 'EMAIL',
            defaultValue: 'test@example.com',
            description: 'Email address',
            trim: true
        )
        string(
            name: 'ADDRESS',
            defaultValue: '',
            description: 'Physical address (optional)',
            trim: true
        )
        string(
            name: 'PHONE',
            defaultValue: '',
            description: 'Phone number (optional)',
            trim: true
        )
        choice(
            name: 'EXECUTION_MODE',
            choices: ['python', 'bash', 'docker'],
            description: 'Which implementation to execute'
        )
    }

    environment {
        SCRIPT_DIR = "${WORKSPACE}"
    }

    stages {
        stage('Validate Parameters') {
            steps {
                script {
                    echo "Validating parameters..."

                    if (params.USERNAME.isEmpty()) {
                        error("USERNAME parameter is required")
                    }

                    if (params.EMAIL.isEmpty()) {
                        error("EMAIL parameter is required")
                    }

                    // Basic email validation
                    if (!params.EMAIL.contains('@')) {
                        error("EMAIL must be a valid email address")
                    }

                    echo "Parameter validation passed"
                }
            }
        }

        stage('Display Parameters') {
            steps {
                echo "Received parameters:"
                echo "  Username: ${params.USERNAME}"
                echo "  Email: ${params.EMAIL}"
                echo "  Address: ${params.ADDRESS ?: 'Not provided'}"
                echo "  Phone: ${params.PHONE ?: 'Not provided'}"
                echo "  Execution Mode: ${params.EXECUTION_MODE}"
            }
        }

        stage('Execute Python Script') {
            when {
                expression { params.EXECUTION_MODE == 'python' }
            }
            steps {
                script {
                    echo "Executing Python implementation..."
                    sh """
                        python3 user_manager.py \
                            '${params.USERNAME}' \
                            '${params.EMAIL}' \
                            '${params.ADDRESS}' \
                            '${params.PHONE}'
                    """
                }
            }
        }

        stage('Execute Bash Script') {
            when {
                expression { params.EXECUTION_MODE == 'bash' }
            }
            steps {
                script {
                    echo "Executing Bash implementation..."
                    sh """
                        chmod +x create_user.sh
                        ./create_user.sh \
                            '${params.USERNAME}' \
                            '${params.EMAIL}' \
                            '${params.ADDRESS}' \
                            '${params.PHONE}'
                    """
                }
            }
        }

        stage('Execute Docker Container') {
            when {
                expression { params.EXECUTION_MODE == 'docker' }
            }
            steps {
                script {
                    echo "Building and executing Docker implementation..."
                    sh """
                        docker build -t user-manager:latest .
                        docker run --rm \
                            -e USERNAME='${params.USERNAME}' \
                            -e EMAIL='${params.EMAIL}' \
                            -e ADDRESS='${params.ADDRESS}' \
                            -e PHONE='${params.PHONE}' \
                            user-manager:latest
                    """
                }
            }
        }

        stage('Report Results') {
            steps {
                script {
                    echo "User account creation process completed"
                    echo "Summary:"
                    echo "  Method used: ${params.EXECUTION_MODE}"
                    echo "  Account created for: ${params.USERNAME}"
                }
            }
        }
    }

    post {
        success {
            echo "Pipeline completed successfully"
        }
        failure {
            echo "Pipeline failed - check logs for details"
        }
        always {
            cleanWs()
        }
    }
}

Takeaways

Each language and tool offers distinct advantages for parameter handling:

Python excels at combining multiple parameter sources with clear type hints and validation. The language's flexibility makes it ideal for complex parameter processing logic.

Bash provides straightforward positional parameters with simple default value syntax. It's particularly effective for system-level scripting where parameters flow directly from command invocation.

Dockerfile separates build-time configuration (ARG) from runtime configuration (ENV), enabling flexible container deployment strategies across different environments.

Jenkins offers a declarative, UI-friendly approach to parameters with built-in validation and type safety. The parameters directive creates an intuitive interface for operators while maintaining programmatic access in pipeline code.

Best Practices

  1. Always validate required parameters before processing begins

  2. Provide sensible defaults for optional parameters

  3. Document parameter purposes through comments or descriptions

  4. Use consistent naming conventions across all implementations

  5. Escape parameters properly when passing between systems to prevent injection vulnerabilities

  6. Consider parameter sensitivity and avoid logging credentials or personal information

Arger

By understanding how each tool handles parameters, you can build robust automation workflows that adapt to different execution contexts while maintaining consistency and reliability.

However, it can be a pain to write the boilerplate code, especially when you change the parameters. Updating the same across the entire pipeline can get boring quick. So, I created an app to generate the code using Opal.

It takes two inputs:

  1. Parameter name and type

  2. Languages to generate the code, like Python, Bash, Jenkinsfile, and Dockerfile.

Then, it will generate the code required to handle these parameters. Here’s the link:

Arger

"Arger" can refer to the German word "Ärger," which means "annoyance," "irritation," or "trouble". It can also refer to the German verb "ärgern," which means "to annoy," "to irritate," or "to anger". Less commonly, "Arger" might be a surname or a personal name of various origins.