iRules Style Guide

This article (formatted here in collaboration with and from the notes of F5er Jim_Deucker) features an opinionated way to write iRules, which is an extension to the Tcl language. Tcl has its own style guide for reference, as do other languages like my personal favorite python. From the latter, Guido van Rossum quotes Ralph Waldo Emerson: "A foolish consistency is the hobgoblin of little minds..," or if you prefer, Morpheus from the Matrix: "What you must learn is that these rules are no different than the rules of a computer system. Some of them can be bent. Others can be broken." The point? This is a guide, and if there is a good reason to break a rule...by all means break it!

Editor Settings

Setting a standard is good for many reasons. It's easier to share code amongst colleagues, peers, or the larger community when code is consistent and professional looking. Settings for some tools are provided below, but if you're using other tools, here's the goal:

  • indent 4 spaces (no tab characters)
  • 100-column goal for line length (120 if you must) but avoid line continuations where possible
  • file parameters
    • ASCII
    • Unix linefeeds (\n)
    • trailing whitespace trimmed from the end of each line
    • file ends with a linefeed

Visual Studio Code

If you aren't using VSCode, why the heck not? This tool is amazing, and with the F5 Networks iRules extension coupled with The F5 Extension, you get the functionality of a powerful editor along with the connectivity control of your F5 hosts. With code diagnostics and auto formatting based on this very guide, the F5 Networks iRules Extension will make your life easy. Seriously...stop reading and go set up VSCode now.

EditorConfig

For those with different tastes in text editing using an editor that supports EditorConfig:

# 4 space indentation
[*.{irule,irul}]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
charset = ascii
trim_trailing_whitespace = true

Vim

I'm a vi guy with sys-admin work, but I prefer a full-fledge IDE for development efforts. If you prefer the file editor, however, we've got you covered with these Vim settings:

# in ~/.vimrc file
set tabstop=4
set shiftwidth=4
set expandtab
set fileencoding=ascii
set fileformat=unix

Sublime

There are a couple tools for sublime, but all of them are a bit dated and might require some work to bring them up to speed. Unless you're already a Sublime apologist, I'd choose one of the other two options above.

Guidance

  1. Watch out for smart-quotes and non-breaking spaces inserted by applications like Microsoft Word as they can silently break code. The VSCode extension will highlight these occurrences and offer a fix automatically, so again, jump on that bandwagon!
  2. A single iRule has a 64KB limit. If you're reaching that limit it might be time to question your life choices, I mean, the wisdom of the solution.
  3. Break out your iRules into functional blocks. Try to separate (where possible) security from app functionality from stats from protocol nuances from mgmt access, etc. For example, when the DevCentral team managed the DevCentral servers and infrastructure, we had 13 iRules to handle maintenance pages, masking application error codes and data, inserting scripts for analytics, managing vanity links and other structural rewrites to name a few. With this strategy, priorities for your events are definitely your friend.
  4. Standardize on "{" placement at the end of a line and not the following line, this causes the least problems across all the BIG-IP versions.
        # ### THIS ###
        if { thing } {
            script
        } else {
            other_script
        }
    
        # ### NOT THIS ###
        if { thing } {
            script
        }
        else {
            other_script
        }
  5. 4-character indent carried into nested values as well, like in switch.
        # ### THIS ###
        switch -- ${thing} {
            "opt1" {
                command
            }
            default {
                command
            }
        }
  6. Comments (as image for this one to preserve line numbers)
    1. Always comment at the same indent-level as the code (lines 1, 4, 9-10)
    2. Avoid end-of-line comments (line 11)
    3. Always hash-space a comment (lines 1, 4, 9-10)
    4. Leave out the space when commenting out code (line 2)
    5. switch statements cannot have comments inline with options (line 6)
  7. Avoid multiple commands on a single line.
        # ### THIS ###
        set host [getfield [HTTP::host] 1]
        set port [getfield [HTTP::host] 2]
    
        # ### NOT THIS ###
        set host [getfield [HTTP::host] 1]; set port [getfield [HTTP::host] 2]
  8. Avoid single-line if statements, even for debug logs.
        # ### THIS ###
        if { ${debug} } {
            log local0. "a thing happened...."
        }
    
        # ### NOT THIS ###
        if { ${debug} } { log local0. "a thing happened..."} 
  9. Even though Tcl allows a horrific number of ways to communicate truthiness, Always express or store state as 0 or 1
    # ### THIS ###
    set f 0
    set t 1
    if { ${f} && ${t} } {
        ...
    }
    # ### NOT THIS ###
    # Valid false values
    set f_values "n no f fal fals false of off"
    # Valid true values
    set t_values "y ye yes t tr tru true on"
    # Set a single valid, but unpreferred, state
    set f [lindex ${f_values} [expr {int(rand()*[llength ${f_values}])}]]
    set t [lindex ${t_values} [expr {int(rand()*[llength ${t_values}])}]]
    
    if { ${f} && ${t} } {
        ...
    }
  10. Always use Tcl standard || and && boolean operators over the F5 special and and or operators in expressions, and use parentheses when you have multiple arguments to be explicitly clear on operations.
        # ### THIS ###
        if { ${state_active} && ${level_gold} } {
            if { (${state} == "IL") || (${state} == "MO") } {
                pool gold_pool
            }
        }
    
        # ### NOT THIS ###
        if { ${state_active} and ${level_gold} } {
            if { ${state} eq "IL" or ${state} eq "MO" } {
                pool gold_pool
            }
        }
  11. Always put a space between a closing curly bracket and an opening one.
    # ### THIS ###
    if { ${foo} } {
        log local0.info "something"
    }
    
    # ### NOT THIS ###
    if { ${foo} }{
        log local0.info "something"
    }
  12. Always wrap expressions in curly brackets to avoid double expansion. (Check out a deep dive on the byte code between the two approaches shown in the picture below)
        # ### THIS ###
        set result [expr {3 * 4}]
    
        # ### NOT THIS ###
        set result [expr 3 * 4]
  13. Always use space separation around variables in expressions such as if statements or expr calls.
  14. Always wrap your variables in curly brackets when referencing them as well.
        # ### THIS ###
        if { ${host} } {
    
        # ### NOT THIS ###
        if { $host } {
  15. Terminate options on commands like switch and table with "--" to avoid argument injection if if you're 100% sure you don't need them. The VSCode iRules extension will throw diagnostics for this. See K15650046 for more details on the security exposure.
        # ### THIS ###
        switch -- [whereis [IP::client_addr] country] {
            "US" {
                table delete -subtable states -- ${state}
            }
        }
    
        # ### NOT THIS ###
        switch [whereis [IP::client_addr] country] {
            "US" {
                table delete -subtable states ${state}
            }
        }
  16. Always use a priority on an event, even if you're 100% sure you don't need them. The default is 500 so use that if you have no other starting point.
  17. Always put a timeout and/or lifetime on table contents. Make sure you really need the table space before settling on that solution, and consider abusing the static:: namespace instead.
  18. Avoid unexpected scope creep with static:: and table variables by assigning prefixes. Lacking a prefix means if multiple rules set or use the variable changing them becomes a race condition on load or rule update.
    when RULE_INIT priority 500 {
        # ### THIS ###
        set static::appname_confvar 1
    
        # ### NOT THIS ###
        set static::confvar 1
    }​
  19. Avoid using static:: for things like debug configurations, it's a leading cause of unintentional log storms and performance hits. If you have to use them for a provable performance reason follow the prefix naming rule.
    # ### THIS ###
    when CLIENT_ACCEPTED priority 500 {
        set debug 1
    }
    
    when HTTP_REQUEST priority 500 {
        if { ${debug} } {
            log local0.debug "some debug message"
        }
    }
    
    # ### NOT THIS ###
    when RULE_INIT priority 500 {
        set static::debug 1
    }
    
    when HTTP_REQUEST priority 500 {
        if { ${static::debug} } {
            log local0.debug "some debug message"
        }
    }
    ​
  20. Comments are fine and encouraged, but don't leave commented-out code in the final version.

Wrapping up that guidance with a final iRule putting it all into practice:

when HTTP_REQUEST priority 500 {
    # block level comments with leading space
    #command commented out
    if { ${a} } {
        command
    }
    if { !${a} } {
        command
    } elseif { ${b} > 2 || ${c} < 3 } {
        command
    } else {
        command
    }
    switch -- ${b} {
        "thing1" -
        "thing2" {
            # thing1 and thing2 business reason
        }
        "thing3" {
            # something else
        }
        default {
            # default branch
        }
    }
    # make precedence explicit with parentheses
    set d [expr { (3 + ${c} ) / 4 }]
    foreach { f } ${e} {
        # always braces around the lists
    }
    foreach { g h i } { j k l m n o p q r }  {
        # so the lists are easy to add to
    }
    for { set i 0 } { ${i} < 10 } { incr i } {
        # clarity of each parameter is good
    }
}

What standards do you follow for your iRules coding styles? Drop a comment below!

Updated May 20, 2024
Version 4.0
  • Hi Jason, 

    looks good now, as long as the list i well created.

    ${e} escaping the list variable wont help if the list itself is brainlessly concatenated. Back in the days, I also used those "$a $b $c" and slightly later those psedo secure "\{$a\} \{$b\} \{$c\}" list creation skills. Those evil techniques can destroy on entire application logic very quick. 

    Cheers, Kai

  • Yep... eq also got switched to == in the two scripts. One step too much to make code less readable for humans... 🤣

    Cheers, Kai