IP Matching Data Profile iApp

Problem this snippet solves:

In an of itself it is not a true iApp in that it does not configure anything on the BIG-IP but it allows for configuration reuse.

Code :

cli admin-partitions {
    update-partition Common
}
cli script f5.meta_utils {
proc md5sum { text } {
    #calcualte the md5 of text
    return [exec /bin/echo -n ${text} | /usr/bin/openssl dgst -md5]
}
proc b64en { str } {
    #Found at http://wiki.tcl.tk/775
    binary scan ${str} B* bits
    switch [expr {[string length ${bits}]%6}] {
        0 {set tail ""}
        2 {append bits 0000; set tail ==}
        4 {append bits 00; set tail =}
    }
    return [string map {
        000000 A 000001 B 000010 C 000011 D 000100 E 000101 F
        000110 G 000111 H 001000 I 001001 J 001010 K 001011 L
        001100 M 001101 N 001110 O 001111 P 010000 Q 010001 R
        010010 S 010011 T 010100 U 010101 V 010110 W 010111 X
        011000 Y 011001 Z 011010 a 011011 b 011100 c 011101 d
        011110 e 011111 f 100000 g 100001 h 100010 i 100011 j
        100100 k 100101 l 100110 m 100111 n 101000 o 101001 p
        101010 q 101011 r 101100 s 101101 t 101110 u 101111 v
        110000 w 110001 x 110010 y 110011 z 110100 0 110101 1
        110110 2 110111 3 111000 4 111001 5 111010 6 111011 7
        111100 8 111101 9 111110 + 111111 /
    } ${bits}]${tail}
}
proc b64de { str } {
   #Found at http://wiki.tcl.tk/775
   set tail [expr [string length ${str}] - [string length [string trimright ${str} =]]]
   set str [string trimright ${str} =]
   set bits [string map {
     A 000000 B 000001 C 000010 D 000011 E 000100 F 000101
     G 000110 H 000111 I 001000 J 001001 K 001010 L 001011
     M 001100 N 001101 O 001110 P 001111 Q 010000 R 010001
     S 010010 T 010011 U 010100 V 010101 W 010110 X 010111
     Y 011000 Z 011001 a 011010 b 011011 c 011100 d 011101
     e 011110 f 011111 g 100000 h 100001 i 100010 j 100011
     k 100100 l 100101 m 100110 n 100111 o 101000 p 101001
     q 101010 r 101011 s 101100 t 101101 u 101110 v 101111
     w 110000 x 110001 y 110010 z 110011 0 110100 1 110101
     2 110110 3 110111 4 111000 5 111001 6 111010 7 111011
     8 111100 9 111101 + 111110 / 111111
   } ${str}]
   set bits [string range ${bits} 0 end-[expr ${tail}*2]]
   set bytes [binary format B* ${bits}]
   return ${bytes} 
}
proc set_service_description_meta { service key value } {
    #create a key/value entry in a service's metadata, if it does exist update it - wrapper to set_description_meta
    return [set_description_meta "sys application service ${service}" ${key} ${value}]
}
proc delete_service_meta { service key } {
    #delete a services' metadata key/value based on a key - wrapper to delete_meta
    return [delete_meta "sys application service ${service}" ${key}]
}
proc get_service_description_meta { service } {
    #get service metadata - a wrapper for get_description_meta
    return [get_description_meta "sys application service ${service}"]
}
proc get_service_description { service } {
    #get service object description - just a wrapper for get_description
    tmsh::cd /Common
    return [get_description "sys application service ${service}"]
}
proc update_service_meta { service key value } {
    #update a services metadata - a wrapper for update_meta
    return [update_meta "sys application service ${service}" ${key} ${value}]
}
proc get_service_meta_value { service key } {
    return [get_meta_value "sys application service ${service}" ${key}]
}
proc get_description { object } {
    #return the description field, if there is nothing init the metadata info
    tmsh::cd /Common
    set desc [lindex [lindex [lindex [tmsh::get_config ${object} description] 0] [llength ${object}]] 1]
    if {[string match ${desc} "none"]} {
        tmsh::modify ${object} description \"Metadata \{[b64en "\{none foo\} [md5sum "\{none foo\}"]"]\}\"
        set desc "Metadata \{[b64en "\{none foo\} [md5sum "\{none foo\}"]"]\}"
    } 
    return ${desc}        
}
proc get_meta_value { object key } {
    set meta [get_description_meta ${object}]
    set index [get_meta_index ${object} ${key}]
    return [lindex ${meta} ${index}]
}
proc get_description_meta { object } {
    #get full metadata of the object returns a list of key/value pairs
    set desc [get_description ${object} ] 
    #init results
    set result "none"
    #regexp for metadata
    if {[regexp {^.*Metadata\s{(.*)}$} ${desc} all r]} {
        set result [b64de ${r}] 
        if {[string match [md5sum [lrange ${result} 0 end-1]] [lindex ${result} end]]} {
            set result [lrange ${result} 0 end-1]
            return ${result} 
        } else {
            return "failed"
        }
    }
}
proc get_meta_index { object key } {
    #get the index of a specific key in an objects metadata, used for delete/update
    set meta [get_description_meta ${object}]
    set count 0
    set found "-1"
    foreach item ${meta} {
        if {[string match [lindex ${item} 0] ${key}]} {
            set found ${count} 
        }
        incr count
    }
    return ${found}
}
proc update_meta { object key value } {
    #update a key with a value in the metatdata for an object returns the final description
    set index [get_meta_index ${object} ${key}]
    set meta [get_description_meta ${object}]
    set desc_only [lrange [get_description ${object}] 0 end-2]
    set count 0
    set buff  ""
    foreach item ${meta} {
        if {[expr ${count} != ${index}]} {
            lappend buff ${item}
        } else {
            lappend buff "${key} ${value}"
        }
        incr count
    }
    set final "${desc_only} Metadata \{[b64en "${buff} [md5sum ${buff}]"]\}"
    tmsh::modify ${object} description \"${final}\"
    return ${final} 
}
proc delete_meta { object key } {
    #delete a metadata key/value based on a key
    set index [get_meta_index ${object} ${key}]
    #desc_only excludes Metadata keyword
    set desc_only [lrange [get_description ${object}] 0 end-2]
    #init count and buff
    set count 0
    set buff  ""
    #look for each metadata item ... looking for the one to NOT append
    foreach item [get_description_meta ${object}] {
        if {[expr ${count} != ${index}]} {
            lappend buff ${item}
        }
        incr count
    }
    set final "${desc_only} Metadata \{[b64en "${buff} [md5sum ${buff}]"]\}"
    tmsh::modify ${object} description \"${final}\"
    return ${final} 
}
proc set_description_meta { object key value} {
    #create a key/value entry in an objects metadata, if it does exist update it
    set found 0
    set description [get_description ${object}]
    set metadata [get_description_meta ${object}]
    foreach s ${metadata} {
        if {[string match [lindex $s 0] ${key}]} {
            set found 1
        }
    }
    #if metadata not found in metadata
    if { ${found} eq 0 } {
        #didn't find key match, there is a description, and there is metadata in that description 
        puts "desc: ${description} [llength ${description}]"
        if {[expr [llength ${description}] > 2]} {
            #There is more than just metadata - grab none metadata
            set final [lrange ${description} 0 end-2]
            #append the key value to the end of the metadata
            lappend metadata "${key} ${value}"
            set final "${final} Metadata \{[b64en "${metadata} [md5sum ${metadata}]"]\}"
        } else {
            #there is ONLY metadata in this description
            lappend metadata "${key} ${value}"
            set final "Metadata \{[b64en "${metadata} [md5sum ${metadata}]"]\}"
        }
        tmsh::modify ${object} description \"${final}\"
        return ${final}
    } else {
        update_meta ${object} ${key} ${value}
    }
}
}
cli script f5.ntr_utils_devcentral {
proc import_service { service name } {
    tmsh::include "f5.meta_utils"
    tmsh::cd /Common
    set fullname ${tmsh::app_name}.app/${tmsh::app_name}
    set cmd "\"tmsh::modify sys application service $fullname execute-action definition\""
    set_service_description_meta ${service} ${fullname} ${cmd}
    set objs [lindex [tmsh::get_config sys application service ${service} one-line] 0]
    namespace eval ${name} {
        proc service_var { objs pre flag } {
            set name [namespace current]
            set parent_obj $pre
            set count 0
            foreach obj $objs {
                 if { ([expr ${count} % 2]) and ($flag eq 0)} {
                    variable $pre_obj $obj

                 } elseif { ([expr ${count} % 2]) and ($flag > 0)} {
                    variable ${parent_obj}__${pre_obj} $obj
                 }
                 if {([llength $obj] > 1) && ($pre_obj ne "column-names") && ($pre_obj ne "rows") } {
                    incr flag
                    ${name}::service_var $obj $pre_obj $flag
                    incr flag -1
                 }
                 set pre_obj $obj
                 incr count
            }
        }
        proc import_tables { tables } {
            set name [namespace current]
            set table_count 0
            foreach table $tables {
                set result ""
                # Only process the even elements
                if {![expr $table_count % 2]} {
                    set row_count 0
                    foreach row [set ${name}::${table}__rows] {
                        set column_count 0
                        foreach column [set ${name}::${table}__column-names] {
                            #puts "Set ${table}(${column}__${row_count}): [lindex [lindex $row 1] $column_count]"
                            lappend result ${column}__${row_count}
                            lappend result [lindex [lindex $row 1] $column_count]
                            lappend result ${column},${row_count}
                            lappend result [lindex [lindex $row 1] $column_count]
                            incr column_count
                        }
                        incr row_count
                    }
                    variable ${table}
                    array set ${table} ${result}
                }
                incr table_count
            }
        }
        proc import_variables { vars } {
            set var_count 0
            foreach var $vars {
                if {![expr $var_count % 2]} {
                    set varname $var
                } else {
                    variable ${varname} [lindex $var 1]
                }
                incr var_count
            }
        }
    }
    ${name}::service_var [lindex $objs 4] "" 0
    ${name}::import_tables [set ${name}::tables]
    ${name}::import_variables [set ${name}::variables]
}
proc service_callback { } {
    tmsh::include "f5.meta_utils"
    set name $tmsh::app_name
    set fullname ${name}.app/${name}
    tmsh::cd /Common
    catch { foreach service [get_service_description_meta $fullname] {
        tmsh::cd /Common
        eval [lindex ${service} 1]
    }
    }
}
proc import_table { table_local type } {
    set row_count 0
    array set row_result {}
    array set col_result {}
    array set rowcol_result {}
    foreach row ${table_local} {
        set element_count 0
        set names ""
        foreach element [split $row "\n"] {
            if { ($element_count > 0) && ($element_count < [expr [llength [split $row "\n"]] - 1])} {
                if { [llength $element] > 1 } {
                    set value "[lindex $element 1]"
                    set column "[lindex $element 0]"
                    lappend ary_result "${column}__${row_count}" 
                    lappend ary_result "${value}" 
                    lappend comma_result "${column},${row_count}" 
                    lappend comma_result "${value}" 
                    lappend row_result($row_count) ${value}
                    lappend col_result($column) ${value}
                    lappend rowcol_result($row_count) "[list $column ${value}]"
                    lappend names $column
                }
            }
            incr element_count
        }
        incr row_count
    }
    switch $type {
        row 
            {
                foreach row [array names row_result] {
                    lappend result $row
                    lappend result $row_result($row)
                }
                set final_result ${result}
            }
        col
            {
                foreach col [array names col_result] {
                    lappend result $col
                    lappend result $col_result($col)
                }
                set final_result ${result}
            }
        names
            {
                set final_result ${name}s
            }
        rowcol
            {
                foreach row [array names rowcol_result] {
                    lappend result $row
                    lappend result $rowcol_result($row)
                }
                set final_result ${result}
            }
        comma
            {
                set final_result $comma_result
            }
        ary
            {
                set final_result $ary_result
            }
        default
            {
                set final_result $ary_result
            }
    }
    return ${final_result}
}
proc get_items_regexp { field_name field args } {
    set results ""
    puts "args: $args"
    append args " recursive"
    set folder [tmsh::pwd]
    if { [catch {
        set objs [tmsh::get_config $args]
        foreach obj $objs {
            set name [tmsh::get_name $obj]
            puts "name: ${name}" 
            puts "obj: $objs"
            set field_value [tmsh::get_field_value $obj $field_name]
            puts "field_value: $field_value"
            puts "field: $field"
            if { [regexp "$field" $field_value] } {
                append results "$folder/"
                append results [format "%s\n" ${name}]
            }
        }

        if { $folder != "/Common" } {
            tmsh::cd "/Common"
            append objs [tmsh::get_config $args]
            tmsh::cd $folder
            foreach obj $objs {
                set name [tmsh::get_name $obj]
                set field_value [tmsh::get_field_value $obj $field_name]
                if { [regexp "$field" $field_value] } {
                    append results "/Common/"
                    append results [format "%s\n" ${name}]
                }
            }
        }
    } err] } {
        puts "Command failed: tmsh::get_config $args\n  $err"
        return ${results}
    }

    return ${results}
}
}
sys application template /Common/f5.ip_match_profile {
    actions {
        definition {
            html-help {
                
            }
            implementation {
                tmsh::include "f5.ntr_utils_devcentral"
                if { [string match $basic__callback "Yes"]} { 
                    puts "calling back!"
                    tmsh::run_proc f5.ntr_utils_devcentral:service_callback
                }
            }
            presentation {
                include "/Common/f5.apl_common"
                section basic {
                    table ips {
                        string network
                        string netmask
                        noyes invert
                    }
                    noyes callback
                    #optional ( callback == "Yes" ) {
                    #    multichoice callbacks display "xlarge" tcl { tmsh::run_proc f5.ntr_utils:get_items_meta_regexp { "sys application service $tmsh::app_name.app/$tmsh::app_name" ".*app.*" } } 
                    #}
                }
                text {
                    basic "IP Matching table"
                    basic.ips "List of IP address"
                    basic.ips.network "Network:"
                    basic.ips.netmask "Netmask:"
                    basic.ips.invert "Excetptions"
                    basic.callback "Do you want to redeploy iApps that depend on this iApp?"
                }
            }
            role-acl none
            run-as none
        }
    }
    description none
    type failover
}
Published Mar 11, 2015
Version 1.0

Was this article helpful?

No CommentsBe the first to comment