22-Dec-2022 08:00 - edited 10-Jan-2023 16:09
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!
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:
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.
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
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
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.
# ### THIS ###
if { thing } {
script
} else {
other_script
}
# ### NOT THIS ###
if { thing } {
script
}
else {
other_script
}
# ### THIS ###
switch -- ${thing} {
"opt1" {
command
}
default {
command
}
}
# ### 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]
# ### THIS ###
if { ${debug} } {
log local0. "a thing happened...."
}
# ### NOT THIS ###
if { ${debug} } { log local0. "a thing happened..."}
# ### 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} } {
...
}
# ### 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
}
}
# ### THIS ###
if { ${foo} } {
log local0.info "something"
}
# ### NOT THIS ###
if { ${foo} }{
log local0.info "something"
}
# ### THIS ###
set result [expr {3 * 4}]
# ### NOT THIS ###
set result [expr 3 * 4]
# ### THIS ###
if { ${host} } {
# ### NOT THIS ###
if { $host } {
# ### 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}
}
}
when RULE_INIT priority 500 {
# ### THIS ###
set static::appname_confvar 1
# ### NOT THIS ###
set static::confvar 1
}
# ### 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"
}
}
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!
Great article. Most things are cosmetical, but some realy important things are mentioned also. I will highlight:
I see in in the wild often scripts that violates against these best practices.
What also could be mentioned is: switch cases do not need any "return" or "break" statements, too not fall through. I saw "return" many times errorneous used in this context.
@Juergen_Mang interesting point on the return in switch. It's not really something for the style guide, I have a another document I also wrote that covers much more stuff, around common mistakes and issues.
I'll look at adding a diagnostic for this to the next major version of the vscode-irules extension.
Hi Jason,
let me answer programmatically....
set feeling_today [list "just woke up" "need a coffe" "better" "Awesome, no traffic jam!"]
foreach x { another big irule coding project } {
set HTTP_RESPONSE "The string with more than 65535 characters cannot be stored in a message."
if { $HTTP_RESPONSE contains "65535 characters" } then {
lappend feeling_today "Frutstration"
set options [expr { int( rand() * 3) }]
switch -exact -- $options {
"0" {
set idea "How about to remove useful comments, so that I can not read my own code in 1 year from now..."
lappend feeling_today "Not gonna do this..."
}
"1" {
set idea "How about to shrink variable names, so that nobody except me understands the code..."
lappend feeling_today "Not gonna do this..."
}
"2" {
set idea "How about to split the iRule in two files and somehow glue them together?"
lappend feeling_today "Life Question: Why is F5 forcing me this?"
}
default {
set solution "A wonder happens and F5 increases the maximum iRule size!"
lappend feeling_today "Still frustation because \$options values are just in the range of 0-2...."
}
}
lappend feeling_today "Getting angry..."
}
}
64k is not a technical limitation - its just a "whatever" decission made in the past. So whats needs to be done that you help me to convince F5 Devs to increase the maximum iRule size?
And please dont tell me to question my life choices. I already did and still continue to code iRules... 😂
Cheers, Kai
Hi Jason,
you may review your topic 10.), since it provides slightest false information. I assume you accidentially just mixed up || vs. && with eq vs == in on scentence?
"eq" or "ne" is different from "==" or "!=".
"==" or "!=" should only be used for numeric comparsions only
"eq" or "ne" should be used for string comparions.
Examples of the math behind == comparsions:
1 == 1.0 is true
1 == 0001 is true
1 == "0x0000001" is true
1 == "\n\n\n\t\t\t1.e0\n\n\n" is also true
1 == 1.000000000000000031337 is also true
10 == 012 is also true
Beside of this using "eq" to compare "strings" is faster than comparing them with "==" (since no shimmering is involved).
The difference of || / && vs. "or" / "and" are not that huge. I consider the differences as personal preferecens... 😉
Cheers, Kai
@Kai_Wilke No, 10. is correct and not talking about string vs auto casting comparison, it's talking about the non-Tcl operators `and` and `or`, which make writing test harnesses hard.
@Kai_Wilke ah I see you mean in the example, I'll get that tidied up and add a point about "eq"/"ne" vs "=="/"!="
Spotted another glitch in the final iRule:
foreach { f } { ${e} } {
# always braces around the lists
}
If $e is holding your list, then just pass the variable without any quotes or curlys to the loop. Otherwise the loop would just receive the string "${e}" but not the content of it...
Cheers, Kai 😉
@Kai_Wilke the outer brackets around the e variable are problematic, not the inner ones. I'll correct this example.
% set e "1 2 3 4 5 6"
1 2 3 4 5 6
% foreach { f g } ${e} { puts "$f $g" }
1 2
3 4
5 6
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