Conditions and branching allow you to create dynamic workflows that adapt based on runtime data, environment variables, and previous step results.

Basic Conditions

Simple Conditional Execution

from kubiya_sdk import Step, Condition, Branch

# Step that only runs in production
production_backup = Step("backup-production-db").tool("pg_dump").inputs(
    database="production",
    backup_location="/secure-backups/"
).condition("${ENVIRONMENT} == 'production'")

# Step that runs on failure
cleanup_step = Step("cleanup-failed-deployment").tool("kubernetes-cleaner").inputs(
    namespace="${ENVIRONMENT}",
    deployment="${SERVICE_NAME}"
).condition("${deploy_service.status} == 'failed'")

Environment-Based Conditions

# Different behavior per environment
notification_step = Step("send-notification").tool("slack").inputs(
    channel="#deployments",
    message="Deployment completed"
).condition("${ENVIRONMENT} in ['staging', 'production']")

# Skip step in development
security_scan = Step("security-vulnerability-scan").tool("security-scanner").inputs(
    image="${DOCKER_IMAGE}:${TAG}",
    severity_threshold="high"
).condition("${ENVIRONMENT} != 'development'")

Branch Structures

Basic Branching

from kubiya_sdk import Branch, Condition

# Branch based on environment
environment_branch = Branch([
    # Production branch - requires approval
    Condition("${ENVIRONMENT} == 'production'").then([
        Step("request-deployment-approval").tool("approval-gate").inputs(
            approvers=["platform-team@company.com"],
            timeout="2h"
        ),
        Step("deploy-to-production").tool("kubernetes-deployer").inputs(
            environment="production",
            replicas=5
        )
    ]),
    
    # Staging branch - automatic deployment
    Condition("${ENVIRONMENT} == 'staging'").then([
        Step("deploy-to-staging").tool("kubernetes-deployer").inputs(
            environment="staging", 
            replicas=2
        )
    ]),
    
    # Default branch for development
    Condition.default().then([
        Step("deploy-to-development").tool("kubernetes-deployer").inputs(
            environment="development",
            replicas=1
        )
    ])
])

Service-Based Branching

# Different deployment strategies per service type
service_deployment_branch = Branch([
    Condition("${SERVICE_TYPE} == 'frontend'").then([
        Step("build-frontend").tool("npm-builder").inputs(
            build_command="npm run build:prod"
        ),
        Step("deploy-to-cdn").tool("cdn-deployer").inputs(
            source_dir="./dist",
            cache_duration="1h"
        )
    ]),
    
    Condition("${SERVICE_TYPE} == 'api'").then([
        Step("run-api-tests").tool("test-runner").inputs(
            test_suite="api-integration"
        ),
        Step("deploy-api-service").tool("kubernetes-deployer").inputs(
            service_type="api",
            health_check_path="/health"
        )
    ]),
    
    Condition("${SERVICE_TYPE} == 'worker'").then([
        Step("deploy-worker-service").tool("kubernetes-deployer").inputs(
            service_type="worker",
            resource_limits={"memory": "2Gi", "cpu": "1000m"}
        )
    ])
])

Complex Condition Logic

Multiple Condition Operators

# Complex boolean logic
complex_condition = Step("high-priority-deployment").tool("priority-deployer").inputs(
    service="${SERVICE_NAME}",
    priority="high"
).condition("""
    (${ENVIRONMENT} == 'production' and ${URGENCY} == 'high') or
    (${ENVIRONMENT} == 'staging' and ${TEST_RESULTS.success_rate} > 0.95) or
    (${FORCE_DEPLOY} == 'true' and ${APPROVED_BY} != '')
""")

# Numeric comparisons
performance_scaling = Step("scale-up-service").tool("kubernetes-scaler").inputs(
    replicas="${CURRENT_REPLICAS * 2}"
).condition("""
    ${CURRENT_LOAD} > 80 and
    ${RESPONSE_TIME} > 500 and
    ${ERROR_RATE} < 0.01
""")

Time-Based Conditions

# Maintenance window conditions
maintenance_deployment = Step("maintenance-deployment").tool("maintenance-deployer").inputs(
    service="${SERVICE_NAME}",
    maintenance_mode=True
).condition("""
    ${CURRENT_HOUR} >= 2 and ${CURRENT_HOUR} <= 4 and
    ${CURRENT_DAY_OF_WEEK} == 'Sunday'
""")

# Business hours condition
business_hours_notification = Step("send-business-hours-alert").tool("slack").inputs(
    channel="#business-critical",
    message="Critical deployment during business hours"
).condition("""
    ${CURRENT_HOUR} >= 9 and ${CURRENT_HOUR} <= 17 and
    ${CURRENT_DAY_OF_WEEK} not in ['Saturday', 'Sunday']
""")

Conditional Step Results

Branching on Step Outputs

# Health check with conditional responses
health_check = Step("service-health-check").tool("health-checker").inputs(
    service_url="${SERVICE_URL}/health"
)

health_response_branch = Branch([
    # Service is healthy - proceed with deployment
    Condition("${health_check.status_code} == 200").then([
        Step("proceed-with-deployment").tool("deployer").inputs(
            service="${SERVICE_NAME}"
        )
    ]),
    
    # Service is degraded - deploy with caution
    Condition("${health_check.status_code} == 503").then([
        Step("cautious-deployment").tool("canary-deployer").inputs(
            service="${SERVICE_NAME}",
            traffic_percentage=10
        )
    ]),
    
    # Service is down - emergency procedures
    Condition("${health_check.status_code} >= 500").then([
        Step("trigger-incident").tool("incident-manager").inputs(
            severity="high",
            service="${SERVICE_NAME}",
            description="Service unhealthy before deployment"
        )
    ])
])

Data-Driven Conditions

# Database migration with data volume checks
data_check = Step("check-data-volume").tool("postgres-query").inputs(
    query="SELECT COUNT(*) as row_count FROM users"
)

migration_strategy_branch = Branch([
    # Small dataset - direct migration
    Condition("${data_check.row_count} < 10000").then([
        Step("direct-migration").tool("flyway").inputs(
            migration_location="db/migration",
            migration_strategy="direct"
        )
    ]),
    
    # Medium dataset - batched migration  
    Condition("${data_check.row_count} < 1000000").then([
        Step("batched-migration").tool("migration-runner").inputs(
            batch_size=1000,
            migration_strategy="batched"
        )
    ]),
    
    # Large dataset - background migration
    Condition("${data_check.row_count} >= 1000000").then([
        Step("background-migration").tool("background-migrator").inputs(
            migration_strategy="background",
            estimated_duration="2h"
        )
    ])
])

Advanced Branching Patterns

Nested Conditions

# Nested branching for complex decision trees
deployment_strategy = Branch([
    Condition("${ENVIRONMENT} == 'production'").then([
        # Production sub-branches
        Branch([
            Condition("${SERVICE_CRITICALITY} == 'high'").then([
                Step("high-criticality-production-deploy").tool("blue-green-deployer")
            ]),
            Condition("${SERVICE_CRITICALITY} == 'medium'").then([
                Step("medium-criticality-production-deploy").tool("rolling-deployer")
            ]),
            Condition.default().then([
                Step("standard-production-deploy").tool("standard-deployer")
            ])
        ])
    ]),
    
    Condition("${ENVIRONMENT} == 'staging'").then([
        # Staging sub-branches  
        Branch([
            Condition("${FEATURE_FLAGS_ENABLED} == 'true'").then([
                Step("feature-flag-staging-deploy").tool("feature-flag-deployer")
            ]),
            Condition.default().then([
                Step("standard-staging-deploy").tool("standard-deployer")
            ])
        ])
    ])
])

Loop-like Conditions

# Retry deployment with backoff
def create_retry_deployment(max_attempts=3):
    steps = []
    
    for attempt in range(1, max_attempts + 1):
        deploy_step = Step(f"deploy-attempt-{attempt}").tool("deployer").inputs(
            service="${SERVICE_NAME}",
            attempt_number=attempt
        )
        
        # Only run if previous attempts failed
        if attempt > 1:
            deploy_step = deploy_step.condition(
                f"${{{deploy_step_name(attempt-1)}.status}} == 'failed'"
            )
        
        # Add exponential backoff delay
        if attempt > 1:
            delay_step = Step(f"backoff-delay-{attempt}").tool("sleep").inputs(
                duration=f"{2**(attempt-1) * 30}s"  # Exponential backoff
            ).condition(
                f"${{{deploy_step_name(attempt-1)}.status}} == 'failed'"
            )
            steps.append(delay_step)
        
        steps.append(deploy_step)
    
    return steps

def deploy_step_name(attempt):
    return f"deploy-attempt-{attempt}"

Condition Functions and Helpers

Built-in Condition Functions

# String operations
string_conditions = Step("process-user-input").tool("input-processor").condition("""
    startswith(${USER_INPUT}, 'deploy:') and
    contains(${USER_INPUT}, ${SERVICE_NAME}) and
    length(${USER_INPUT}) > 10
""")

# List operations  
list_conditions = Step("multi-service-operation").tool("bulk-processor").condition("""
    ${SERVICE_NAME} in ${ALLOWED_SERVICES} and
    size(${AFFECTED_SERVICES}) <= 5
""")

# Numeric operations
numeric_conditions = Step("resource-intensive-task").tool("heavy-processor").condition("""
    ${AVAILABLE_MEMORY} > 4096 and
    ${CPU_CORES} >= 4 and
    round(${LOAD_AVERAGE}) < 2.0
""")

Custom Condition Functions

# Define custom condition functions
def is_business_critical(service_name):
    critical_services = ["payment", "authentication", "checkout"]
    return service_name in critical_services

def within_maintenance_window(current_time):
    import datetime
    maintenance_start = datetime.time(2, 0)  # 2 AM
    maintenance_end = datetime.time(4, 0)    # 4 AM
    return maintenance_start <= current_time <= maintenance_end

# Use in conditions
critical_service_step = Step("critical-service-deployment").tool("blue-green-deployer").condition(
    f"is_business_critical('${SERVICE_NAME}')"
)

Error Handling with Conditions

Conditional Error Recovery

# Recovery strategy based on error type
error_recovery_branch = Branch([
    Condition("contains(${deployment.error_message}, 'insufficient resources')").then([
        Step("scale-down-non-critical").tool("resource-manager").inputs(
            action="scale_down",
            target="non-critical-services"
        ),
        Step("retry-deployment").tool("deployer").inputs(
            service="${SERVICE_NAME}",
            retry_attempt=True
        )
    ]),
    
    Condition("contains(${deployment.error_message}, 'image not found')").then([
        Step("build-missing-image").tool("docker-builder").inputs(
            service="${SERVICE_NAME}",
            tag="${IMAGE_TAG}"
        ),
        Step("retry-with-new-image").tool("deployer").inputs(
            service="${SERVICE_NAME}",
            force_pull=True
        )
    ]),
    
    # Default error handling
    Condition.default().then([
        Step("escalate-to-humans").tool("incident-manager").inputs(
            severity="medium",
            description="Deployment failed with unknown error"
        )
    ])
])

Graceful Degradation

# Fallback deployment strategy
primary_deployment = Step("primary-deployment-strategy").tool("advanced-deployer").inputs(
    strategy="blue-green"
)

fallback_branch = Branch([
    Condition("${primary_deployment.status} == 'success'").then([
        Step("deployment-success-notification").tool("slack").inputs(
            message="✅ Advanced deployment completed successfully"
        )
    ]),
    
    # Fallback to simpler strategy
    Condition("${primary_deployment.status} == 'failed'").then([
        Step("fallback-deployment").tool("simple-deployer").inputs(
            strategy="rolling-update"
        ),
        Step("fallback-notification").tool("slack").inputs(
            message="⚠️  Fell back to rolling deployment strategy"
        )
    ])
])

Performance Considerations

Lazy Evaluation

# Expensive condition evaluation - use lazy evaluation
expensive_condition = Step("resource-intensive-task").tool("heavy-processor").condition(
    # Only evaluate expensive operations if basic conditions pass first
    "${ENVIRONMENT} == 'production' and expensive_check(${DATA_VOLUME})"
)

Condition Caching

# Cache condition results for reuse
cached_health_check = Step("health-check-once").tool("health-checker").inputs(
    service_url="${SERVICE_URL}"
).cache_result(duration="5m")  # Cache for 5 minutes

# Multiple steps can use the cached result
step1 = Step("operation-1").condition("${cached_health_check.is_healthy}")
step2 = Step("operation-2").condition("${cached_health_check.is_healthy}")

Best Practices

Condition Design

  • Keep conditions simple and readable
  • Use meaningful variable names
  • Document complex logic
  • Test all condition branches

Performance

  • Avoid expensive operations in conditions
  • Use lazy evaluation when possible
  • Cache frequently used condition results
  • Consider condition evaluation order

Error Handling

  • Plan for condition evaluation failures
  • Provide meaningful error messages
  • Use default branches for unexpected cases
  • Test edge cases and boundary conditions

Maintainability

  • Extract complex conditions into functions
  • Use consistent condition patterns
  • Document business logic behind conditions
  • Version control condition changes carefully