diff --git a/src/modules/punk/args-999999.0a1.0.tm b/src/modules/punk/args-999999.0a1.0.tm index 74de69f7..224befcf 100644 --- a/src/modules/punk/args-999999.0a1.0.tm +++ b/src/modules/punk/args-999999.0a1.0.tm @@ -3559,6 +3559,7 @@ tcl::namespace::eval punk::args { #puts "-arg_info->$arg_info" set flagsreceived [list] ;#for checking if required flags satisfied set solosreceived [list] + set multisreceived [list] #secondary purpose: #for -multple true, we need to ensure we can differentiate between a default value and a first of many that happens to match the default. #-default value must not be appended to if argname not yet in flagsreceived @@ -3771,6 +3772,9 @@ tcl::namespace::eval punk::args { } else { tcl::dict::lappend opts $fullopt $flagval } + if {$fullopt ni $multisreceived} { + lappend multisreceived $fullopt + } } else { tcl::dict::set opts $fullopt $flagval } @@ -3790,6 +3794,9 @@ tcl::namespace::eval punk::args { } else { tcl::dict::lappend opts $fullopt 1 } + if {$fullopt ni $multisreceived} { + lappend multisreceived $fullopt + } } else { tcl::dict::set opts $fullopt 1 } @@ -3819,6 +3826,9 @@ tcl::namespace::eval punk::args { if {[tcl::dict::get $argstate $a -type] ne "none"} { if {[tcl::dict::get $argstate $a -multiple]} { tcl::dict::lappend opts $a $newval + if {$a ni $multisreceived} { + lappend multisreceived $a + } } else { tcl::dict::set opts $a $newval } @@ -3836,6 +3846,9 @@ tcl::namespace::eval punk::args { } else { tcl::dict::lappend opts $a 1 } + if {$a ni $multisreceived} { + lappend multisreceived $a + } } else { tcl::dict::set opts $a 1 } @@ -4337,7 +4350,7 @@ tcl::namespace::eval punk::args { foreach e_check $vlist_check { if {![tcl::string::is list -strict $e_check]} { set msg "Option '$argname' for %caller% requires type 'list'. Received: '$e_check'" - return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $e -argspecs $argspecs]] $msg + return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $e_check -argspecs $argspecs]] $msg #arg_error "Option $argname for [Get_caller] requires type 'list'. Received: '$e_check'" $argspecs -badarg $argname } if {[tcl::dict::size $thisarg_checks]} { @@ -4347,7 +4360,7 @@ tcl::namespace::eval punk::args { # -1 for disable is as good as zero if {[llength $e_check] < $checkval} { set msg "Option '$argname for %caller% requires list with -minsize $checkval. Received len:[llength $e_check]" - return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list sizeviolation $type minsize $checkval] -badarg $e -badval $e_check -argspecs $argspecs]] $msg + return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list sizeviolation $type minsize $checkval] -badarg $e_check -badval $e_check -argspecs $argspecs]] $msg #arg_error "Option $argname for [Get_caller] requires list with -minsize $checkval. Received len:[llength $e_check] value:'$e_check'" $argspecs -badarg $argname } } @@ -4355,7 +4368,7 @@ tcl::namespace::eval punk::args { if {$checkval ne "-1"} { if {[llength $e_check] > $checkval} { set msg "Option '$argname for %caller% requires list with -maxsize $checkval. Received len:[llength $e_check]" - return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list sizeviolation $type maxsize $checkval] -badarg $e -badval $e_check -argspecs $argspecs]] $msg + return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list sizeviolation $type maxsize $checkval] -badarg $e_check -badval $e_check -argspecs $argspecs]] $msg #arg_error "Option $argname for [Get_caller] requires list with -maxsize $checkval. Received len:[llength $e_check] value:'$e_check'" $argspecs -badarg $argname } } @@ -4703,7 +4716,7 @@ tcl::namespace::eval punk::args { #(e.g using 'dict exists $received -flag') # - but it can have duplicate keys when args/opts have -multiple 1 #It is actually a list of paired elements - return [tcl::dict::create leaders $leaders_dict opts $opts values $values_dict received $received_posns solos $solosreceived] + return [tcl::dict::create leaders $leaders_dict opts $opts values $values_dict received $received_posns solos $solosreceived multis $multisreceived] } #proc sample1 {p1 args} { diff --git a/src/modules/punk/libunknown-0.1.tm b/src/modules/punk/libunknown-0.1.tm index 24e2abc7..6f01e340 100644 --- a/src/modules/punk/libunknown-0.1.tm +++ b/src/modules/punk/libunknown-0.1.tm @@ -872,8 +872,30 @@ tcl::namespace::eval punk::libunknown { set ok_forgets [list] foreach p $forgets_requested { #'package files' not avail in early 8.6 - if {$p ni {tcl Tcl} && (!$has_package_files || [package provide $p] eq "" || ($has_package_files && [package provide $p] ne "" && [llength [package files $p]] > 0))} { - lappend ok_forgets $p + #There can be other custom 'package ifneeded' scripts that don't use source - but still need to be forgotten. + #a basic/trivial case: 'package ifneeded aaa 0.1.1 {package provide aaa 0.1.1}' + #it could also use 'eval' instead of sourcing. + #For this reason - we shouldn't use 'package files' as any sort of indication of forgetability + #if {$p ni {tcl Tcl} && (!$has_package_files || [package provide $p] eq "" || ($has_package_files && [package provide $p] ne "" && [llength [package files $p]] > 0))} { + # lappend ok_forgets $p + #} + #What then? Hardcoded only for now? + if {$p ni {tcl Tcl tcl::oo}} { + #tcl::oo returns a comment only for its package provide script "# Already present, OK?" + # - so we can't use empty 'ifneeded' script as a determinant. + set vpresent [package provide $p] + if {$vpresent ne ""} { + #There could theoretically be other ifneeded scripts registered - but if the one in use is empty + #we'll use that as the criteria to disallow forget - REVIEW + set ifneededscript [package ifneeded $p $vpresent] + if {[string trim $ifneededscript] ne ""} { + lappend ok_forgets $p + } + } else { + #not loaded - but may have registered ifneeded script(s) in the package database + #assume ok to forget + lappend ok_forgets $p + } } } if {[llength $ok_forgets]} { diff --git a/src/modules/punk/netbox-999999.0a1.0.tm b/src/modules/punk/netbox-999999.0a1.0.tm index 78ff9e44..cef961c6 100644 --- a/src/modules/punk/netbox-999999.0a1.0.tm +++ b/src/modules/punk/netbox-999999.0a1.0.tm @@ -119,7 +119,8 @@ tcl::namespace::eval punk::netbox::system { directive -id matching the name" endpoint -help\ "The subpath to be appended to the base url. - e.g api/ipam/ip-addresses/" + e.g api/ipam/ip-addresses/ + api/ipam/ip-addresses/{id}/" -verb -default get -choices {get post patch head put delete} -body -default optional -choicecolumns 2 -choices {none optional required mime_multipart}\ -choicelabels { @@ -141,6 +142,25 @@ tcl::namespace::eval punk::netbox::system { and the mime part body, in this order." } } + + #A somewhat sanitized config for outputting to stderr etc + #Obscure at least full Authorization Token + #todo - other headers? + proc obscured_config {cfg} { + set sanconfig $cfg + if {[dict exists $cfg headers]} { + set hdrs [dict get $cfg headers] + if {[dict exists $hdrs Authorization]} { + set auth [dict get $hdrs Authorization] + if {[dict exists $auth Token]} { + dict set auth Token "[string range [dict get $auth Token] 0 5]..." + dict set hdrs Authorization $auth + dict set sanconfig headers $hdrs + } + } + } + return $sanconfig + } proc make_rest_func {args} { set argd [punk::args::parse $args withid ::punk::netbox::system::make_rest_func] lassign [dict values $argd] leaders opts values received @@ -155,8 +175,11 @@ tcl::namespace::eval punk::netbox::system { %endpoint% $endpoint\ %verb% $verb\ %body% $body\ - %showdict% {!@@results @@results/@*/@*.@*}\ - %showdict2% {@@results/@*/@*.@* !@@results}\ + %showpagedict% {!@@results @@results/@*/@*.@*}\ + %showpagedict2% {@@results/@*/@*.@* !@@results}\ + %showdict% {*}\ + %showdict2% {*/*}\ + %showlistofdicts% {@*/@*.@*}\ ] if {$commandname eq "::punk::netbox::status"} { #we get duplicate django-version for %showdict% - todo - something. @@ -166,7 +189,7 @@ tcl::namespace::eval punk::netbox::system { set procbody [string map $custom { set argd [punk::args::parse $args withid %commandname%] - lassign [dict values $argd] leaders opts values received + lassign [dict values $argd] leaders opts values received solos multis set apicontextid [dict get $leaders apicontextid] if {[dict exists $received -RETURN]} { set returntype [dict get $opts -RETURN] @@ -180,13 +203,19 @@ tcl::namespace::eval punk::netbox::system { } } - set query [dict create] - dict for {k v} $opts { + set FORCE 1 + if {[dict exists $opts -FORCE]} { + set FORCE [dict get $opts -FORCE] + } + + set query [list] ;#use list not dict - allows repeated params + dict for {k val} $opts { switch -- $k { -CUSTOM_PARAM { - foreach custval $v { - lassign $custval param value - dict set query $param $value + #'-multiple true' + foreach kv $val { + lassign $kv paramname value + lappend query $paramname $value } } -RETURN { @@ -194,13 +223,25 @@ tcl::namespace::eval punk::netbox::system { } default { if {[string match *_FILTER $k]} { - set field [string range [string tolower [lindex [split $k _] 0]] 1 end] ;# -NAME_FILTER -> name - foreach fv $v { - lassign $fv filter value - dict set query ${field}__$filter $value + #all _FILTER methods are '-multiple true' - so are present in $opts as a single key with a value that is a list + #e.g -X_Y_FILTER "blah" + set parts [split $k _] + set name1 [string range [string tolower [lindex $parts 0]] 1 end] ;#strip leading dash off first part + set nametail [string tolower [lrange $parts 1 end-1]] + set paramname [join [list $name1 {*}$nametail] _] + foreach fv $val { + lassign $fv filter value ;#filter is n,gte,lte etc + lappend query ${paramname}__$filter $value } } else { - dict set query [string range $k 1 end] $v + set paramname [string range $k 1 end] + if {$paramname in $multis} { + foreach v $val { + lappend query $paramname $v + } + } else { + lappend query $paramname $val + } } } } @@ -233,10 +274,14 @@ tcl::namespace::eval punk::netbox::system { dict set config headers [list Authorization [list Token [dict get $contexts $apicontextid token value]]] - if {$returntype eq "json"} { + if {$returntype in "json jsondump"} { #if we set result json - we get a dict instead of json :/ dict set config result raw } + if {$body in {required optional}} { + #content type for the request data + dict set config headers content-type "application/json" + } #variable headerdict #set config [dict create\ @@ -244,23 +289,64 @@ tcl::namespace::eval punk::netbox::system { #] set url [dict get $contexts $apicontextid url value] - puts "${url}%endpoint% '$query' '$config'" - if {$body in {required optional}} { - set result [::rest::%verb% ${url}%endpoint% $query $config $requestbody] + set endpoint "%endpoint%" + if {[string first {{id}} $endpoint] != -1} { + set id [dict get $values id] + set endpoint [string map [list {{id}} $id] $endpoint] + } + #todo - only show if debug (and obscure Authorization Token) + set sanconfig [punk::netbox::system::obscured_config $config] + puts stderr "url:${url}$endpoint query:'$query' verb:%verb% config:'$sanconfig'" + if {$FORCE} { + #FORCE is true for most operations (and no -FORCE option even available) but the option exists and defaults to false for specifically unsafe + #e.g delete operations on entire endpoints + if {$body in {required optional}} { + set result [::rest::%verb% ${url}$endpoint $query $config $requestbody] + } else { + set result [::rest::%verb% ${url}$endpoint $query $config] + } } else { - set result [::rest::%verb% ${url}%endpoint% $query $config] + puts stderr "%commandname% not called because -FORCE is false" + set sanconfig [punk::netbox::system::obscured_config $config] + puts "url:${url}$endpoint query:'$query' verb:%verb% config:'$sanconfig'" + return } switch -exact -- $returntype { - showdict { + showpagedict { #return [punk::lib::showdict $result !@@results @@results/@*/@*.@*] + return [punk::lib::showdict $result %showpagedict%] + } + showpagedict2 { + #return [punk::lib::showdict $result @@results/@*/@*.@* !@@results] + return [punk::lib::showdict $result %showpagedict2%] + } + showdict { return [punk::lib::showdict $result %showdict%] } showdict2 { - #return [punk::lib::showdict $result @@results/@*/@*.@* !@@results] return [punk::lib::showdict $result %showdict2%] } + showlist { + return [punk::lib::showdict -roottype list $result] + } + showlistofdicts { + return [punk::lib::showdict $result %showlistofdicts%] + } + jsondump { + package require huddle::json + #pretty-print via huddle (inefficient review) + set h [huddle::json::json2huddle parse $result] + return [huddle::jsondump $h] + } + linelist { + set ret "" + foreach r $result { + append ret $r \n + } + return $ret + } default { - #dict or json - the counterintuitive 'result' field above sets this + #plain result: (list or dict) or json - the counterintuitive 'result' field set to raw above sets the rest resulting format to json return $result } } @@ -731,9 +817,13 @@ tcl::namespace::eval punk::netbox { } set _tenant_options { -tenant_group_id + -tenant_group_id__n -tenant_group + -tenant_group__n -tenant_id + -tenant_id__n -tenant + -tenant__n } set _region_options { -region_id @@ -741,17 +831,33 @@ tcl::namespace::eval punk::netbox { } set _site_options { -site_group_id + -site_group_id__n -site_group + -site_group__n -site_id + -site_id__n -site + -site__n } set _group_options { -group_id + -group_id__n -group + -group__n + } + set _contact_options { + -contact + -contact__n + -contact_role + -contact_role__n + -contact_group + -contact_group__n } set _role_options { -role_id + -role_id__n -role + -role__n } set _filter_string [list\ "ie \n Exact match\n(case-insensitive)"\ @@ -765,13 +871,35 @@ tcl::namespace::eval punk::netbox { "niew \n Does not end with\n (case-insensitive)"\ "empty \n Is empty/null"\ ] + set _filter_number [list\ + "n \n Not equal to"\ + "lte \n Less than or equal"\ + "lt \n Less than"\ + "gte \n Greater than or equal"\ + "gt \n Greater than"\ + ] set _CUSTOM_PARAMS { -CUSTOM_PARAM -type list -minsize 2 -maxsize 2 -multiple 1 -help\ "Specify a parameter not in this API e.g -CUSTOM_PARAM {mytag blah}" } - set _RETURN { - -RETURN -type string -choices {dict showdict showdict2 json} -choicelabels { + set _RETURN_PAGEDICT { + -RETURN -type string -choices {dict showpagedict showpagedict2 json jsondump} -choicelabels { + dict\ + " Tcl dictionary + (fastest)" + showpagedict\ + " human readable dict display + with same order as dict." + showpagedict2\ + " human readable dict display + results first, page metadata last." + } -help\ + "Options for returned data. + Note that showdict results are relatively slow, especially for large resultsets" + } + set _RETURN_DICT { + -RETURN -type string -choices {dict showdict showdict2 json jsondump} -choicelabels { dict\ " Tcl dictionary (fastest)" @@ -785,8 +913,34 @@ tcl::namespace::eval punk::netbox { "Options for returned data. Note that showdict results are relatively slow, especially for large resultsets" } + set _RETURN_LIST { + -RETURN -type string -choices {list linelist showlist json jsondump} -choicelabels { + list\ + " Tcl list + (fastest)" + linelist\ + " raw list with newline after each item" + showlist\ + " human readable list display" + } -help\ + "Options for returned data. + Note that showlist results are relatively slow, especially for large resultsets" + } + set _RETURN_LISTOFDICTS { + -RETURN -type string -choices {list linelist showlist json jsondump} -choicelabels { + list\ + " Tcl list + (fastest)" + linelist\ + " raw list with newline after each item" + showlistofdicts\ + " human readable display list of dicts" + } -help\ + "Options for returned data. + Note that showlist results are relatively slow, especially for large resultsets" + } set _RETURN_STATUS { - -RETURN -type string -default showdict2 -choices {dict showdict showdict2 json} -choicelabels { + -RETURN -type string -default showdict2 -choices {dict showdict showdict2 json jsondump} -choicelabels { dict\ " Tcl dictionary" showdict\ @@ -806,6 +960,11 @@ tcl::namespace::eval punk::netbox { set string_filter_help "Paired search filter for string:\n" append _string_filter_help [textblock::list_as_table -columns 4 -show_hseps 1 $_filter_string] + + #n, lte, lt, gte, gt + #e.g virtualization/virtual-machine vcpus, memory, disk + set number_filter_help "Paired search filter for number:\n" + append _number_filter_help [textblock::list_as_table -columns 3 -show_hseps 1 $_filter_number] } punk::args::define {*}[list\ @@ -920,14 +1079,104 @@ tcl::namespace::eval punk::netbox { } # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +tcl::namespace::eval punk::netbox::dcim { + namespace export {[a-z]*} + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::dcim::devices_list + @cmd -name punk::netbox::dcim::devices_list -help\ + "tenancy_tenants_list + GET request for endpoint /dcim/devices/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -name + -NAME_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_name_filter_help}} + -asset_tag -type string + -ASSET_TAG_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + -face -type string + -face__n -type string + -position -type integer + -POSITION_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -airflow -type string + -airflow__n -type string + -vc_position -type integer + -VC_POSITION_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -vc_priority -type integer + -VC_PRIORITY_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + }\ + [set ::punk::netbox::argdoc::_create_update_options]\ + { + -q -type string + -tag -type string + -tag__n -type string + }\ + [set ::punk::netbox::argdoc::_tenant_options]\ + [set ::punk::netbox::argdoc::_contact_options]\ + { + -local_context_data + -manufacturer_id + -manufacturer_id__n + -manufacturer + -manufacturer__n + -device_type_id + -device_type_id__n + -role_id + -role_id__n + -role + -role__n + -parent_device_id + -parent_device_id__n + -platform_id + -platform_id__n + -platform + -platform__n + }\ + [set ::punk::netbox::argdoc::_group_options]\ + [set ::punk::netbox::argdoc::_region_options]\ + [set ::punk::netbox::argdoc::_site_options]\ + { + -location_id -type integer + -location_id__n -type integer + -rack_id -type integer + -rack_id__n -type integer + -cluster_id -type integer + -cluster_id__n -type integer + -model -type string + -model__n -type string + -status -type string + -status__n -type string + -mac_address -type string + -MAC_ADDRESS_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + -serial -type string + -SERIAL_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + -virtual_chassis_id -type integer + -virtual_chassis_id__n -type integer + }\ + [set ::punk::netbox::argdoc::_page_options]\ + [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ + { + @values -min 0 -max 0 + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::dcim::devices_list api/dcim/devices/ -verb get -body none +} tcl::namespace::eval punk::netbox::ipam { namespace export {[a-z]*} lappend PUNKARGS [list\ { @dynamic - @id -id ::punk::netbox::ipam::vrfs - @cmd -name punk::netbox::ipam::vrfs -help\ + @id -id ::punk::netbox::ipam::vrfs_list + @cmd -name punk::netbox::ipam::vrfs_list -help\ "ipam_vrfs_list GET request for endpoint /ipam/vrfs/" @leaders -min 1 -max 1 @@ -939,6 +1188,7 @@ tcl::namespace::eval punk::netbox::ipam { -choices {${[punk::netbox::api_context_names]}} @opts -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -name -NAME_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_name_filter_help}} -rd -type string -help\ @@ -964,19 +1214,42 @@ tcl::namespace::eval punk::netbox::ipam { }\ [set ::punk::netbox::argdoc::_page_options]\ [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ - [set ::punk::netbox::argdoc::_RETURN]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ { @values -min 0 -max 0 }] - ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::vrfs api/ipam/vrfs/ -verb get -body none + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::vrfs_list api/ipam/vrfs/ -verb get -body none + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::ipam::vrfs_read + @cmd -name punk::netbox::ipam::vrfs_read -help\ + "ipam_vrfs_list + GET request for endpoint /ipam/vrfs/{id}" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + id -type integer -help\ + "A unique integer value identifying this VRF" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::vrfs_read api/ipam/vrfs/{id}/ -verb get -body none punk::args::define {*}[list\ { @dynamic - @id -id ::punk::netbox::ipam::prefixes - @cmd -name punk::netbox::ipam::prefixes -help\ + @id -id ::punk::netbox::ipam::prefixes_list + @cmd -name punk::netbox::ipam::prefixes_list -help\ "ipam_prefixes_list GET request for endpoint /ipam/prefixes/" @leaders -min 1 -max 1 @@ -988,6 +1261,7 @@ tcl::namespace::eval punk::netbox::ipam { -choices {${[punk::netbox::api_context_names]}} @opts -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -is_pool -mark_utilized -description -type string -help "Exact Match (case sensitive)" @@ -995,7 +1269,8 @@ tcl::namespace::eval punk::netbox::ipam { }\ [set ::punk::netbox::argdoc::_create_update_options]\ { - -q + -q -type string -help\ + "Query prefixes by substring" -tag }\ [set ::punk::netbox::argdoc::_tenant_options]\ @@ -1010,31 +1285,154 @@ tcl::namespace::eval punk::netbox::ipam { -within_include -contains -depth + -DEPTH_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -children + -CHILDREN_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -mask_length -mask_length__gte -mask_length__lte + -vlan_id -type integer + -vlan_id__n -type integer + -vlan_vid -type integer + -VLAN_VID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -vrf_id -vrf - -role_id - -role -status -available_on_device -available_on_virtualmachine }\ [set ::punk::netbox::argdoc::_page_options]\ [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ - [set ::punk::netbox::argdoc::_RETURN]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ { @values -min 0 -max 0 }] - ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes api/ipam/prefixes/ -verb get -body none + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes_list api/ipam/prefixes/ -verb get -body none + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::prefixes_create + @cmd -name punk::netbox::ipam::prefixes_create -help\ + "ipam_prefixes_create + POST request for endpoint /ipam/prefixes/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + body -type string -help\ + "JSON string" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes_create api/ipam/prefixes/{id}/ -verb post -body required + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::prefixes_read + @cmd -name punk::netbox::ipam::prefixes_read -help\ + "ipam_prefixes_read + GET request for endpoint /ipam/prefixes/{id}/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + id -type integer -help\ + "A unique integer value identifying this prefix" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes_read api/ipam/prefixes/{id}/ -verb get -body none + + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::prefixes_available-ips_list + @cmd -name punk::netbox::ipam::prefixes_available-ips_list -help\ + "ipam_prefixes_available-ips_list + GET request for endpoint /ipam/prefixes/{id}/available-ips/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_page_options]\ + [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ + [set ::punk::netbox::argdoc::_RETURN_LISTOFDICTS]\ + { + @values -min 1 -max 1 + id -type integer -help\ + "A unique integer value identifying this prefix" + }\ + ] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes_available-ips_list api/ipam/prefixes/{id}/available-ips/ -verb get -body none punk::args::define {*}[list\ { @dynamic - @id -id ::punk::netbox::ipam::ip-addresses - @cmd -name punk::netbox::ipam::ip-addresses -help\ + @id -id ::punk::netbox::ipam::prefixes_available-ips_create + @cmd -name punk::netbox::ipam::prefixes_available-ips_create -help\ + "ipam_prefixes_available-ips_create + POST request for endpoint /ipam/prefixes/{id}/available-ips/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 2 + id -type integer -help\ + "A unique integer value identifying this prefix" + body -type string -default "" -help\ + { + If empty create a single IP with default values. + (next available IP in prefix) + + Create 2 IPs: + [ + {"description": "ip1"}, + {"description": "ip2"} + ] + NOTE: This always uses next available IPs. + To create a specific IP, use api/ipam/ip-addresses endpoint. + + The returned json is just an object if one address created, + but a list if multiple. :/ + + } + }\ + ] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes_available-ips_create api/ipam/prefixes/{id}/available-ips/ -verb post -body required + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::ip-addresses_list + @cmd -name punk::netbox::ipam::ip-addresses_list -help\ "ipam_ip-addresses_list GET request for endpoint /ipam/ip-addresses/" @leaders -min 1 -max 1 @@ -1046,6 +1444,7 @@ tcl::namespace::eval punk::netbox::ipam { -choices {${[punk::netbox::api_context_names]}} @opts -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -dns_name -DNS_NAME_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} -description -type string -help "Exact Match (case sensitive)" @@ -1087,13 +1486,359 @@ tcl::namespace::eval punk::netbox::ipam { }\ [set ::punk::netbox::argdoc::_page_options]\ [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ - [set ::punk::netbox::argdoc::_RETURN]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ { @values -min 0 -max 0 }] - ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::ip-addresses api/ipam/ip-addresses/ -verb get -body none + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::ip-addresses_list api/ipam/ip-addresses/ -verb get -body none + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::ip-addresses_read + @cmd -name punk::netbox::ipam::ip-addresses_read -help\ + "ipam_ip-addresses_read + GET request for endpoint /ipam/ip-addresses/{id}/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + id -type integer + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::ip-addresses_read api/ipam/ip-addresses/{id}/ -verb get -body none + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::ip-addresses_create + @cmd -name punk::netbox::ipam::ip-addresses_create -help\ + "ipam_ip-addresses_create + POST request for endpoint /ipam/ip-addresses/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + body -type string -help\ + {JSON string + Example: + { + "address": "string", + "vrf": 0, + "tenant": 0, + "status": "active", + "role": "loopback", + "assigned_object_type": "string", + "assigned_object_id": 0, + "nat_inside": 0, + "dns_name": "string", + "description": "string", + "tags": [ + { + "name": "string", + "slug": "string", + "color": "string" + } + ], + "custom_fields": {} + } + Required: address (IPv4 or IPV6 address with mask) + } + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::ip-addresses_create api/ipam/ip-addresses/ -verb post -body required + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::ip-addresses_bulk_partial_update + @cmd -name punk::netbox::ipam::ip-addresses_bulk_partial_update -help\ + "ipam_ip-addresses_bulk_partical_update + PATCH request for endpoint /ipam/ip-addresses/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + body -type string -help\ + {JSON string + model: + { + "address": "string", + "vrf": 0, + "tenant": 0, + "status": "active", + "role": "loopback", + "assigned_object_type": "string", + "assigned_object_id": 0, + "nat_inside": 0, + "dns_name": "string", + "description": "string", + "tags": [ + { + "name": "string", + "slug": "string", + "color": "string" + } + ], + "custom_fields": {} + } + required: address + } + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::ip-addresses_bulk_partial_update api/ipam/ip-addresses/ -verb patch -body required + +} +tcl::namespace::eval punk::netbox::tenancy { + namespace export {[a-z]*} + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::tenancy::tenants_list + @cmd -name punk::netbox::tenancy::tenants_list -help\ + "tenancy_tenants_list + GET request for endpoint /tenancy/tenants/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -name + -NAME_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_name_filter_help}} + -slug -type string + -SLUG_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + -description -type string + -DESCRIPTION_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + }\ + [set ::punk::netbox::argdoc::_create_update_options]\ + { + -q -type string + -tag -type string + -tag__n -type string + }\ + [set ::punk::netbox::argdoc::_tenant_options]\ + [set ::punk::netbox::argdoc::_contact_options]\ + { + }\ + { + }\ + [set ::punk::netbox::argdoc::_group_options]\ + { + }\ + [set ::punk::netbox::argdoc::_page_options]\ + [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ + { + @values -min 0 -max 0 + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::tenancy::tenants_list api/tenancy/tenants/ -verb get -body none +} + +tcl::namespace::eval punk::netbox::virtualization { + namespace export {[a-z]*} + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::virtualization::virtual-machines_list + @cmd -name punk::netbox::virtualization::virtual-machines_list -help\ + "virtualization_virtual-machines_list + GET request for endpoint /virtualization/virtual-machines/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -name + -NAME_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_name_filter_help}} + -cluster -type string + -cluster_n -type string + -vcpus -type integer + -VCPUS_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -memory -type integer -help\ + "Whole number" + -MEMORY_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -disk -type integer + -DISK_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + }\ + [set ::punk::netbox::argdoc::_create_update_options]\ + { + -q + -tag + }\ + [set ::punk::netbox::argdoc::_tenant_options]\ + [set ::punk::netbox::argdoc::_contact_options]\ + { + -local_context_data + -status + -status_n + -cluster_group_id + -cluster_group_id__n + -cluster_group + -cluster_group__n + -cluster_type_id + -cluster_type_id__n + -cluster_type + -cluster_type__n + -cluster_id + -cluster_id__n + }\ + [set ::punk::netbox::argdoc::_region_options]\ + [set ::punk::netbox::argdoc::_site_options]\ + { + -platform + -platform__n + -mac_address + -MAC_ADDRESS_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + -has_primary_ip + }\ + [set ::punk::netbox::argdoc::_group_options]\ + [set ::punk::netbox::argdoc::_role_options]\ + { + }\ + [set ::punk::netbox::argdoc::_page_options]\ + [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ + { + @values -min 0 -max 0 + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::virtualization::virtual-machines_list api/virtualization/virtual-machines/ -verb get -body none + + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::virtualization::virtual-machines_create + @cmd -name punk::netbox::virtualization::virtual-machines_create -help\ + "virtualization_virtual-machines_create + GET request for endpoint /virtualization/virtual-machines/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 2 -max 2 + id -type integer -help\ + "A unique integer value identifying this virtual machine" + body -type string -help\ + "JSON string" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::virtualization::virtual-machines_create api/virtualization/virtual-machines/ -verb post -body required + + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::virtualization::virtual-machines_delete + @cmd -name punk::netbox::virtualization::virtual-machines_delete -help\ + "virtualization_virtual-machines_delete + DELETE request for endpoint /virtualization/virtual-machines/ + HTTP code: 204 + " + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + -FORCE -default 0 -type boolean -help\ + "Set to true to BULK delete all items at this endpoint" + }\ + { + @values -min 0 -max 0 + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::virtualization::virtual-machines_delete api/virtualization/virtual-machines/ -verb delete -body none + + + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::virtualization::virtual-machines_read + @cmd -name punk::netbox::virtualization::virtual-machines_read -help\ + "virtualization_virtual-machines_read + GET request for endpoint /virtualization/virtual-machines/{id}" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + id -type integer -help\ + "A unique integer value identifying this virtual machine" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::virtualization::virtual-machines_read api/virtualization/virtual-machines/{id}/ -verb get -body none + + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::virtualization::virtual-machines_update + @cmd -name punk::netbox::virtualization::virtual-machines_update -help\ + "virtualization_virtual-machines_update + PUT request for endpoint /virtualization/virtual-machines/{id}" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 2 -max 2 + id -type integer -help\ + "A unique integer value identifying this virtual machine" + body -type string -help\ + "JSON string" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::virtualization::virtual-machines_update api/virtualization/virtual-machines/{id}/ -verb put -body required } + + # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # Secondary API namespace # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ @@ -1186,6 +1931,7 @@ tcl::namespace::eval punk::netbox { punk::args::lib::tstr -return string { netbox /status/ endpoint beginnings of /ipam/ endpoints + beginnings of /virtualization/ endpoints } } # ------------------------------------------------------------- @@ -1222,7 +1968,12 @@ tcl::namespace::eval punk::netbox { # variable PUNKARGS_aliases namespace eval ::punk::args::register { #use fully qualified so 8.6 doesn't find existing var in global namespace - lappend ::punk::args::register::NAMESPACES ::punk::netbox ::punk::netbox::ipam + lappend ::punk::args::register::NAMESPACES\ + ::punk::netbox\ + ::punk::netbox::dcim\ + ::punk::netbox::ipam\ + ::punk::netbox::tenancy\ + ::punk::netbox::virtualization } # ----------------------------------------------------------------------------- diff --git a/src/modules/punk/netbox/man-999999.0a1.0.tm b/src/modules/punk/netbox/man-999999.0a1.0.tm new file mode 100644 index 00000000..7d88468f --- /dev/null +++ b/src/modules/punk/netbox/man-999999.0a1.0.tm @@ -0,0 +1,601 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from -buildversion.txt +# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm +# +# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. +# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# (C) 2025 +# +# @@ Meta Begin +# Application punk::netbox::man 999999.0a1.0 +# Meta platform tcl +# Meta license +# @@ Meta End + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# doctools header +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#*** !doctools +#[manpage_begin shellspy_module_punk::netbox::man 0 999999.0a1.0] +#[copyright "2025"] +#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] +#[moddesc {-}] [comment {-- Description at end of page heading --}] +#[require punk::netbox::man] +#[keywords module] +#[description] +#[para] - + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[section Overview] +#[para] overview of punk::netbox::man +#[subsection Concepts] +#[para] - + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[subsection dependencies] +#[para] packages used by punk::netbox::man +#[list_begin itemized] + +package require Tcl 8.6- +package require punk::netbox +package require uri +package require rest + +#*** !doctools +#[item] [package {Tcl 8.6}] +#[item] [package {punk::netbox}] + +# #package require frobz +# #*** !doctools +# #[item] [package {frobz}] + +#*** !doctools +#[list_end] + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[section API] + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# oo::class namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#tcl::namespace::eval punk::netbox::man::class { + #*** !doctools + #[subsection {Namespace punk::netbox::man::class}] + #[para] class definitions + #if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} { + #*** !doctools + #[list_begin enumerated] + + # oo::class create interface_sample1 { + # #*** !doctools + # #[enum] CLASS [class interface_sample1] + # #[list_begin definitions] + + # method test {arg1} { + # #*** !doctools + # #[call class::interface_sample1 [method test] [arg arg1]] + # #[para] test method + # puts "test: $arg1" + # } + + # #*** !doctools + # #[list_end] [comment {-- end definitions interface_sample1}] + # } + + #*** !doctools + #[list_end] [comment {--- end class enumeration ---}] + #} +#} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +tcl::namespace::eval punk::netbox::man { + namespace export {[a-z]*} + variable PUNKARGS + + #review + ? + proc uri_part_decode {uripart} { + set specialMap {"[" "%5B" "]" "%5D" + " "} + set seqRE {%([0-9a-fA-F]{2})} + set replacement {[format "%c" [scan "\1" "%2x"]]} + set modstr [regsub -all $seqRE [string map $specialMap $uripart] $replacement] + return [encoding convertfrom utf-8 [subst -nobackslash -novariable $modstr]] + } + + proc uri_get_querystring_as_keyval_list {uri} { + set parts [uri::split $uri] + set query ?[dict get $parts query] + set raw_plist [rest::parameters $query] ;#not a dict - can have repeated params (important for _FILTER methods) + return [lmap v $raw_plist {uri_part_decode $v}] + } +} + + +tcl::namespace::eval punk::netbox::man::prefixes { + # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + # Base namespace + # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + #*** !doctools + #[subsection {Namespace punk::netbox::man}] + #[para] Core API functions for punk::netbox::man + #[list_begin definitions] + + namespace export {[a-z]*} + namespace ensemble create + + variable PUNKARGS + lappend PUNKARGS [::list\ + [punk::args::resolved_def -antiglobs {apicontextid @leaders @values -RETURN} -override {@id {-id "::punk::netbox::man::prefixes list"}} ::punk::netbox::ipam::prefixes_list]\ + {-RETURN -default table -choices {table tableobject list}}\ + {@values -min 0 -max 0}\ + ] + + #caution: must use ::list to avoid loop + proc list {args} { + set argd [punk::args::parse $args withid "::punk::netbox::man::prefixes list"] + set token tclread ;#todo + + set next "" + set requests_allowed 1000 ;#review + set resultlist [::list] + set opts [dict get $argd opts] + set vals [dict get $argd values] + set multis [dict get $argd multis] + set outer_return [dict get $opts -RETURN] + set opts [dict remove $opts -RETURN] ;#opts from punk::args::parse is a dict (no dup keys) - can use 'dict remove' safely + #we can't just pass through 'multi' opts even if only one was supplied - list level is wrong + set nextopts [::list] + dict for {opt val} $opts { + if {$opt ni $multis} { + lappend nextopts $opt $val + } else { + foreach v $val { + lappend nextopts $opt $v + } + } + } + #Now opts is a list with possible repeated options! (for flags that have -multiple true) + + while {$next ne "null"} { + if {$next ni [::list "" null]} { + set plist [punk::netbox::man::uri_get_querystring_as_keyval_list $next] + #don't use any dict write operations on plist/nextopts - can destroy dup keys + set p_offset [lsearch -stride 2 $plist offset] ;#only search in 'key' positions - for -offset we are only expecting/allowing a single entry + if {$p_offset != -1} { + lappend nextopts -offset [lindex $plist $p_offset+1] + } + set p_limit [lsearch -stride 2 $plist limit] + if {$p_limit != -1} { + lappend nextopts -limit [lindex $plist $p_limit+1] + } + } + puts "-->next:$next nextopts:$nextopts vals:$vals" + set resultd [punk::netbox::ipam::prefixes_list $token {*}$nextopts -RETURN dict {*}$vals] + set next [dict get $resultd next] + set batch [dict get $resultd results] + lappend resultlist {*}$batch + incr requests_allowed -1 + if {$requests_allowed < 1} {break} + } + if {$outer_return in {table tableobject}} { + package require textblock + set t [textblock::list_as_table -return tableobject -colheaders {id prefix family vrf tenant children status vlan description _depth}] + foreach pfx $resultlist { + if {[dict exists $pfx tenant id]} { + set tenant "[dict get $pfx tenant id]: [dict get $pfx tenant slug]" + } else { + set tenant [dict get $pfx tenant] ;#probably null + } + if {[dict exists $pfx vlan id]} { + set vlan "[dict get $pfx vlan id]: [dict get $pfx vlan display]" + } else { + set vlan [dict get $pfx vlan] ;#probably null + } + set r [::list\ + [dict get $pfx id]\ + [dict get $pfx display]\ + [dict get $pfx family label]\ + [dict get $pfx vrf id]\ + $tenant\ + [dict get $pfx children]\ + [dict get $pfx status value]\ + $vlan\ + [dict get $pfx description]\ + [dict get $pfx _depth]\ + ] + $t add_row $r + } + } + switch -- $outer_return { + table { + set result [$t print] + $t destroy + return $result + } + tableobject { + return $t + } + } + return $resultlist + #return [showdict $resultd] + } + + + #lappend PUNKARGS [::list\ + # [punk::args::resolved_def -antiglobs {apicontextid @leaders @values -RETURN} -override {@id {-id "::punk::netbox::man::prefixes available-ips_list"}} ::punk::netbox::ipam::prefixes_available-ips_list]\ + # {-RETURN -default table -choices {table tableobject list}} + # ] + lappend PUNKARGS [::list\ + [punk::args::resolved_def\ + -antiglobs {apicontextid @leaders -offset}\ + -override {\ + @id {-id "::punk::netbox::man::prefixes available-ips_list"}\ + -limit {-default 254 -help "Maximum number of entries to return"}\ + -RETURN {-default table -choices {table tableobject list linelist}}\ + @values {-min 1 -max 1}\ + }\ + ::punk::netbox::ipam::prefixes_available-ips_list\ + ]\ + ] + + proc available-ips_list {args} { + set argd [punk::args::parse $args withid "::punk::netbox::man::prefixes available-ips_list"] + set token tclread ;#todo + + set next "" + set requests_allowed 1000 ;#review + set resultlist [::list] + set opts [dict get $argd opts] + set valuedict [dict get $argd values] + set vals [dict values $valuedict] ;#we don't need the keys to pass on to next func + set multis [dict get $argd multis] + set outer_return [dict get $opts -RETURN] + set opts [dict remove $opts -RETURN] ;#opts from punk::args::parse is a dict (no dup keys) - can use 'dict remove' safely + #we can't just pass through 'multi' opts even if only one was supplied - list level is wrong + set nextopts [::list] + dict for {opt val} $opts { + if {$opt ni $multis} { + lappend nextopts $opt $val + } else { + foreach v $val { + lappend nextopts $opt $v + } + } + } + #Now opts is a list with possible repeated options! (for flags that have -multiple true) + + #No paging available at endpoint ipam/prefixes/available-ips - but we can still use limit (but offset doesn't seem to work + set resultlist [punk::netbox::ipam::prefixes_available-ips_list $token {*}$nextopts -RETURN list {*}$vals] + + if {$outer_return in {table tableobject}} { + package require textblock + set t [textblock::list_as_table -return tableobject -colheaders {address family vrf}] + foreach ip $resultlist { + set r [::list\ + [dict get $ip address]\ + [dict get $ip family]\ + "[dict get $ip vrf id]: [dict get $ip vrf name]"\ + ] + $t add_row $r + } + } + switch -- $outer_return { + table { + set result [$t print] + $t destroy + return $result + } + tableobject { + return $t + } + linelist { + set ret "" + foreach r $resultlist { + append ret $r \n + } + return $ret + } + jsondump { + #todo + package require huddle::json + #pretty-print via huddle (inefficient review) + set h [huddle::json::json2huddle parse $result] + return [huddle::jsondump $h] + } + default { + return $resultlist + } + } + #return [showdict $resultd] + } + + + + #*** !doctools + #[list_end] [comment {--- end definitions namespace punk::netbox::man ---}] +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + +tcl::namespace::eval punk::netbox::man::ip-addresses { + namespace export {[a-z]*} + namespace ensemble create + variable PUNKARGS + + lappend PUNKARGS [::list\ + [punk::args::resolved_def -antiglobs {apicontextid @leaders @values -RETURN} -override {@id {-id "::punk::netbox::man::ip-addresses list"}} ::punk::netbox::ipam::ip-addresses_list]\ + {-RETURN -default table -choices {table tableobject list}}\ + {@values -min 0 -max 0}\ + ] + + #caution: must use ::list to avoid loop + proc list {args} { + set argd [punk::args::parse $args withid "::punk::netbox::man::ip-addresses list"] + set token tclread ;#todo + + set next "" + set requests_allowed 1000 ;#review + set resultlist [::list] + set opts [dict get $argd opts] + set vals [dict get $argd values] + set multis [dict get $argd multis] + set outer_return [dict get $opts -RETURN] + set opts [dict remove $opts -RETURN] ;#opts from punk::args::parse is a dict (no dup keys) - can use 'dict remove' safely + #we can't just pass through 'multi' opts even if only one was supplied - list level is wrong + set nextopts [::list] + dict for {opt val} $opts { + if {$opt ni $multis} { + lappend nextopts $opt $val + } else { + foreach v $val { + lappend nextopts $opt $v + } + } + } + #Now opts is a list with possible repeated options! (for flags that have -multiple true) + + while {$next ne "null"} { + if {$next ni [::list "" null]} { + set plist [punk::netbox::man::uri_get_querystring_as_keyval_list $next] + #don't use any dict write operations on plist/nextopts - can destroy dup keys + set p_offset [lsearch -stride 2 $plist offset] ;#only search in 'key' positions - for -offset we are only expecting/allowing a single entry + if {$p_offset != -1} { + lappend nextopts -offset [lindex $plist $p_offset+1] + } + set p_limit [lsearch -stride 2 $plist limit] + if {$p_limit != -1} { + lappend nextopts -limit [lindex $plist $p_limit+1] + } + } + puts "-->next:$next nextopts:$nextopts vals:$vals" + set resultd [punk::netbox::ipam::ip-addresses_list $token {*}$nextopts -RETURN dict {*}$vals] + set next [dict get $resultd next] + set batch [dict get $resultd results] + lappend resultlist {*}$batch + incr requests_allowed -1 + if {$requests_allowed < 1} {break} + } + if {$outer_return in {table tableobject}} { + package require textblock + set t [textblock::list_as_table -return tableobject -colheaders {id address family vrf_id tenant status assigned_object_type deviceinfo dns_name description}] + foreach ip $resultlist { + if {[dict exists $ip tenant id]} { + set tenant "[dict get $ip tenant id]: [dict get $ip tenant slug]" + } else { + set tenant [dict get $ip tenant] ;#probably null + } + switch -- [dict get $ip assigned_object_type] { + dcim.interface { + set device_id [dict get $ip assigned_object device id] + set device_display [dict get $ip assigned_object device display] + set deviceinfo "$device_id: $device_display" + } + virtualization.vminterface { + set deviceinfo "vm" + } + default { + set deviceinfo - + } + } + set r [::list\ + [dict get $ip id]\ + [dict get $ip address]\ + [dict get $ip family label]\ + [dict get $ip vrf id]\ + $tenant\ + [dict get $ip status value]\ + [dict get $ip assigned_object_type]\ + $deviceinfo\ + [dict get $ip dns_name]\ + [dict get $ip description]\ + ] + $t add_row $r + } + } + switch -- $outer_return { + table { + set result [$t print] + $t destroy + return $result + } + tableobject { + return $t + } + } + return $resultlist + #return [showdict $resultd] + } + + +} + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# Secondary API namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +tcl::namespace::eval punk::netbox::man::lib { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + tcl::namespace::path [tcl::namespace::parent] + #*** !doctools + #[subsection {Namespace punk::netbox::man::lib}] + #[para] Secondary functions that are part of the API + #[list_begin definitions] + + #proc utility1 {p1 args} { + # #*** !doctools + # #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]] + # #[para]Description of utility1 + # return 1 + #} + + + + #*** !doctools + #[list_end] [comment {--- end definitions namespace punk::netbox::man::lib ---}] +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#*** !doctools +#[section Internal] +#tcl::namespace::eval punk::netbox::man::system { + #*** !doctools + #[subsection {Namespace punk::netbox::man::system}] + #[para] Internal functions that are not part of the API + + + +#} + + +# == === === === === === === === === === === === === === === +# Sample 'about' function with punk::args documentation +# == === === === === === === === === === === === === === === +tcl::namespace::eval punk::netbox::man { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + variable PUNKARGS + variable PUNKARGS_aliases + + lappend PUNKARGS [list { + @id -id "(package)punk::netbox::man" + @package -name "punk::netbox::man" -help\ + "Package + Description" + }] + + namespace eval argdoc { + #namespace for custom argument documentation + proc package_name {} { + return punk::netbox::man + } + proc about_topics {} { + #info commands results are returned in an arbitrary order (like array keys) + set topic_funs [info commands [namespace current]::get_topic_*] + set about_topics [list] + foreach f $topic_funs { + set tail [namespace tail $f] + lappend about_topics [string range $tail [string length get_topic_] end] + } + #Adjust this function or 'default_topics' if a different order is required + return [lsort $about_topics] + } + proc default_topics {} {return [list Description *]} + + # ------------------------------------------------------------- + # get_topic_ functions add more to auto-include in about topics + # ------------------------------------------------------------- + proc get_topic_Description {} { + punk::args::lib::tstr [string trim { + package punk::netbox::man + A management wrapper over the punk::netbox + REST API. + } \n] + } + proc get_topic_License {} { + return "" + } + proc get_topic_Version {} { + return "$::punk::netbox::man::version" + } + proc get_topic_Contributors {} { + set authors { Julian Noble} + set contributors "" + foreach a $authors { + append contributors $a \n + } + if {[string index $contributors end] eq "\n"} { + set contributors [string range $contributors 0 end-1] + } + return $contributors + } + proc get_topic_custom-topic {} { + punk::args::lib::tstr -return string { + todo - next available ip-address from prefix + } + } + # ------------------------------------------------------------- + } + + # we re-use the argument definition from punk::args::standard_about and override some items + set overrides [dict create] + dict set overrides @id -id "::punk::netbox::man::about" + dict set overrides @cmd -name "punk::netbox::man::about" + dict set overrides @cmd -help [string trim [punk::args::lib::tstr { + About punk::netbox::man + }] \n] + dict set overrides topic -choices [list {*}[punk::netbox::man::argdoc::about_topics] *] + dict set overrides topic -choicerestricted 1 + dict set overrides topic -default [punk::netbox::man::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict + set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *] + lappend PUNKARGS [list $newdef] + proc about {args} { + package require punk::args + #standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on + set argd [punk::args::parse $args withid ::punk::netbox::man::about] + lassign [dict values $argd] _leaders opts values _received + punk::args::package::standard_about -package_about_namespace ::punk::netbox::man::argdoc {*}$opts {*}[dict get $values topic] + } +} +# end of sample 'about' function +# == === === === === === === === === === === === === === === + + +# ----------------------------------------------------------------------------- +# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked +# ----------------------------------------------------------------------------- +# variable PUNKARGS +# variable PUNKARGS_aliases +namespace eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES\ + ::punk::netbox::man\ + ::punk::netbox::man::prefixes\ + ::punk::netbox::man::ip-addresses + +} +# ----------------------------------------------------------------------------- + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Ready +package provide punk::netbox::man [tcl::namespace::eval punk::netbox::man { + variable pkg punk::netbox::man + variable version + set version 999999.0a1.0 +}] +return + +#*** !doctools +#[manpage_end] + diff --git a/src/modules/punk/netbox/man-buildversion.txt b/src/modules/punk/netbox/man-buildversion.txt new file mode 100644 index 00000000..f47d01c8 --- /dev/null +++ b/src/modules/punk/netbox/man-buildversion.txt @@ -0,0 +1,3 @@ +0.1.0 +#First line must be a semantic version number +#all other lines are ignored. diff --git a/src/vfs/_vfscommon.vfs/modules/punk/args-0.1.4.tm b/src/vfs/_vfscommon.vfs/modules/punk/args-0.1.4.tm index e1256fe4..da03207b 100644 --- a/src/vfs/_vfscommon.vfs/modules/punk/args-0.1.4.tm +++ b/src/vfs/_vfscommon.vfs/modules/punk/args-0.1.4.tm @@ -3559,6 +3559,7 @@ tcl::namespace::eval punk::args { #puts "-arg_info->$arg_info" set flagsreceived [list] ;#for checking if required flags satisfied set solosreceived [list] + set multisreceived [list] #secondary purpose: #for -multple true, we need to ensure we can differentiate between a default value and a first of many that happens to match the default. #-default value must not be appended to if argname not yet in flagsreceived @@ -3771,6 +3772,9 @@ tcl::namespace::eval punk::args { } else { tcl::dict::lappend opts $fullopt $flagval } + if {$fullopt ni $multisreceived} { + lappend multisreceived $fullopt + } } else { tcl::dict::set opts $fullopt $flagval } @@ -3790,6 +3794,9 @@ tcl::namespace::eval punk::args { } else { tcl::dict::lappend opts $fullopt 1 } + if {$fullopt ni $multisreceived} { + lappend multisreceived $fullopt + } } else { tcl::dict::set opts $fullopt 1 } @@ -3819,6 +3826,9 @@ tcl::namespace::eval punk::args { if {[tcl::dict::get $argstate $a -type] ne "none"} { if {[tcl::dict::get $argstate $a -multiple]} { tcl::dict::lappend opts $a $newval + if {$a ni $multisreceived} { + lappend multisreceived $a + } } else { tcl::dict::set opts $a $newval } @@ -3836,6 +3846,9 @@ tcl::namespace::eval punk::args { } else { tcl::dict::lappend opts $a 1 } + if {$a ni $multisreceived} { + lappend multisreceived $a + } } else { tcl::dict::set opts $a 1 } @@ -4337,7 +4350,7 @@ tcl::namespace::eval punk::args { foreach e_check $vlist_check { if {![tcl::string::is list -strict $e_check]} { set msg "Option '$argname' for %caller% requires type 'list'. Received: '$e_check'" - return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $e -argspecs $argspecs]] $msg + return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $e_check -argspecs $argspecs]] $msg #arg_error "Option $argname for [Get_caller] requires type 'list'. Received: '$e_check'" $argspecs -badarg $argname } if {[tcl::dict::size $thisarg_checks]} { @@ -4347,7 +4360,7 @@ tcl::namespace::eval punk::args { # -1 for disable is as good as zero if {[llength $e_check] < $checkval} { set msg "Option '$argname for %caller% requires list with -minsize $checkval. Received len:[llength $e_check]" - return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list sizeviolation $type minsize $checkval] -badarg $e -badval $e_check -argspecs $argspecs]] $msg + return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list sizeviolation $type minsize $checkval] -badarg $e_check -badval $e_check -argspecs $argspecs]] $msg #arg_error "Option $argname for [Get_caller] requires list with -minsize $checkval. Received len:[llength $e_check] value:'$e_check'" $argspecs -badarg $argname } } @@ -4355,7 +4368,7 @@ tcl::namespace::eval punk::args { if {$checkval ne "-1"} { if {[llength $e_check] > $checkval} { set msg "Option '$argname for %caller% requires list with -maxsize $checkval. Received len:[llength $e_check]" - return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list sizeviolation $type maxsize $checkval] -badarg $e -badval $e_check -argspecs $argspecs]] $msg + return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list sizeviolation $type maxsize $checkval] -badarg $e_check -badval $e_check -argspecs $argspecs]] $msg #arg_error "Option $argname for [Get_caller] requires list with -maxsize $checkval. Received len:[llength $e_check] value:'$e_check'" $argspecs -badarg $argname } } @@ -4703,7 +4716,7 @@ tcl::namespace::eval punk::args { #(e.g using 'dict exists $received -flag') # - but it can have duplicate keys when args/opts have -multiple 1 #It is actually a list of paired elements - return [tcl::dict::create leaders $leaders_dict opts $opts values $values_dict received $received_posns solos $solosreceived] + return [tcl::dict::create leaders $leaders_dict opts $opts values $values_dict received $received_posns solos $solosreceived multis $multisreceived] } #proc sample1 {p1 args} { diff --git a/src/vfs/_vfscommon.vfs/modules/punk/libunknown-0.1.tm b/src/vfs/_vfscommon.vfs/modules/punk/libunknown-0.1.tm index 24e2abc7..6f01e340 100644 --- a/src/vfs/_vfscommon.vfs/modules/punk/libunknown-0.1.tm +++ b/src/vfs/_vfscommon.vfs/modules/punk/libunknown-0.1.tm @@ -872,8 +872,30 @@ tcl::namespace::eval punk::libunknown { set ok_forgets [list] foreach p $forgets_requested { #'package files' not avail in early 8.6 - if {$p ni {tcl Tcl} && (!$has_package_files || [package provide $p] eq "" || ($has_package_files && [package provide $p] ne "" && [llength [package files $p]] > 0))} { - lappend ok_forgets $p + #There can be other custom 'package ifneeded' scripts that don't use source - but still need to be forgotten. + #a basic/trivial case: 'package ifneeded aaa 0.1.1 {package provide aaa 0.1.1}' + #it could also use 'eval' instead of sourcing. + #For this reason - we shouldn't use 'package files' as any sort of indication of forgetability + #if {$p ni {tcl Tcl} && (!$has_package_files || [package provide $p] eq "" || ($has_package_files && [package provide $p] ne "" && [llength [package files $p]] > 0))} { + # lappend ok_forgets $p + #} + #What then? Hardcoded only for now? + if {$p ni {tcl Tcl tcl::oo}} { + #tcl::oo returns a comment only for its package provide script "# Already present, OK?" + # - so we can't use empty 'ifneeded' script as a determinant. + set vpresent [package provide $p] + if {$vpresent ne ""} { + #There could theoretically be other ifneeded scripts registered - but if the one in use is empty + #we'll use that as the criteria to disallow forget - REVIEW + set ifneededscript [package ifneeded $p $vpresent] + if {[string trim $ifneededscript] ne ""} { + lappend ok_forgets $p + } + } else { + #not loaded - but may have registered ifneeded script(s) in the package database + #assume ok to forget + lappend ok_forgets $p + } } } if {[llength $ok_forgets]} { diff --git a/src/vfs/_vfscommon.vfs/modules/punk/netbox-0.1.0.tm b/src/vfs/_vfscommon.vfs/modules/punk/netbox-0.1.0.tm index aa0e71de..020bb7a9 100644 --- a/src/vfs/_vfscommon.vfs/modules/punk/netbox-0.1.0.tm +++ b/src/vfs/_vfscommon.vfs/modules/punk/netbox-0.1.0.tm @@ -119,7 +119,8 @@ tcl::namespace::eval punk::netbox::system { directive -id matching the name" endpoint -help\ "The subpath to be appended to the base url. - e.g api/ipam/ip-addresses/" + e.g api/ipam/ip-addresses/ + api/ipam/ip-addresses/{id}/" -verb -default get -choices {get post patch head put delete} -body -default optional -choicecolumns 2 -choices {none optional required mime_multipart}\ -choicelabels { @@ -141,6 +142,25 @@ tcl::namespace::eval punk::netbox::system { and the mime part body, in this order." } } + + #A somewhat sanitized config for outputting to stderr etc + #Obscure at least full Authorization Token + #todo - other headers? + proc obscured_config {cfg} { + set sanconfig $cfg + if {[dict exists $cfg headers]} { + set hdrs [dict get $cfg headers] + if {[dict exists $hdrs Authorization]} { + set auth [dict get $hdrs Authorization] + if {[dict exists $auth Token]} { + dict set auth Token "[string range [dict get $auth Token] 0 5]..." + dict set hdrs Authorization $auth + dict set sanconfig headers $hdrs + } + } + } + return $sanconfig + } proc make_rest_func {args} { set argd [punk::args::parse $args withid ::punk::netbox::system::make_rest_func] lassign [dict values $argd] leaders opts values received @@ -155,8 +175,11 @@ tcl::namespace::eval punk::netbox::system { %endpoint% $endpoint\ %verb% $verb\ %body% $body\ - %showdict% {!@@results @@results/@*/@*.@*}\ - %showdict2% {@@results/@*/@*.@* !@@results}\ + %showpagedict% {!@@results @@results/@*/@*.@*}\ + %showpagedict2% {@@results/@*/@*.@* !@@results}\ + %showdict% {*}\ + %showdict2% {*/*}\ + %showlistofdicts% {@*/@*.@*}\ ] if {$commandname eq "::punk::netbox::status"} { #we get duplicate django-version for %showdict% - todo - something. @@ -166,7 +189,7 @@ tcl::namespace::eval punk::netbox::system { set procbody [string map $custom { set argd [punk::args::parse $args withid %commandname%] - lassign [dict values $argd] leaders opts values received + lassign [dict values $argd] leaders opts values received solos multis set apicontextid [dict get $leaders apicontextid] if {[dict exists $received -RETURN]} { set returntype [dict get $opts -RETURN] @@ -180,13 +203,19 @@ tcl::namespace::eval punk::netbox::system { } } - set query [dict create] - dict for {k v} $opts { + set FORCE 1 + if {[dict exists $opts -FORCE]} { + set FORCE [dict get $opts -FORCE] + } + + set query [list] ;#use list not dict - allows repeated params + dict for {k val} $opts { switch -- $k { -CUSTOM_PARAM { - foreach custval $v { - lassign $custval param value - dict set query $param $value + #'-multiple true' + foreach kv $val { + lassign $kv paramname value + lappend query $paramname $value } } -RETURN { @@ -194,13 +223,25 @@ tcl::namespace::eval punk::netbox::system { } default { if {[string match *_FILTER $k]} { - set field [string range [string tolower [lindex [split $k _] 0]] 1 end] ;# -NAME_FILTER -> name - foreach fv $v { - lassign $fv filter value - dict set query ${field}__$filter $value + #all _FILTER methods are '-multiple true' - so are present in $opts as a single key with a value that is a list + #e.g -X_Y_FILTER "blah" + set parts [split $k _] + set name1 [string range [string tolower [lindex $parts 0]] 1 end] ;#strip leading dash off first part + set nametail [string tolower [lrange $parts 1 end-1]] + set paramname [join [list $name1 {*}$nametail] _] + foreach fv $val { + lassign $fv filter value ;#filter is n,gte,lte etc + lappend query ${paramname}__$filter $value } } else { - dict set query [string range $k 1 end] $v + set paramname [string range $k 1 end] + if {$paramname in $multis} { + foreach v $val { + lappend query $paramname $v + } + } else { + lappend query $paramname $val + } } } } @@ -233,10 +274,14 @@ tcl::namespace::eval punk::netbox::system { dict set config headers [list Authorization [list Token [dict get $contexts $apicontextid token value]]] - if {$returntype eq "json"} { + if {$returntype in "json jsondump"} { #if we set result json - we get a dict instead of json :/ dict set config result raw } + if {$body in {required optional}} { + #content type for the request data + dict set config headers content-type "application/json" + } #variable headerdict #set config [dict create\ @@ -244,23 +289,64 @@ tcl::namespace::eval punk::netbox::system { #] set url [dict get $contexts $apicontextid url value] - puts "${url}%endpoint% '$query' '$config'" - if {$body in {required optional}} { - set result [::rest::%verb% ${url}%endpoint% $query $config $requestbody] + set endpoint "%endpoint%" + if {[string first {{id}} $endpoint] != -1} { + set id [dict get $values id] + set endpoint [string map [list {{id}} $id] $endpoint] + } + #todo - only show if debug (and obscure Authorization Token) + set sanconfig [punk::netbox::system::obscured_config $config] + puts stderr "url:${url}$endpoint query:'$query' verb:%verb% config:'$sanconfig'" + if {$FORCE} { + #FORCE is true for most operations (and no -FORCE option even available) but the option exists and defaults to false for specifically unsafe + #e.g delete operations on entire endpoints + if {$body in {required optional}} { + set result [::rest::%verb% ${url}$endpoint $query $config $requestbody] + } else { + set result [::rest::%verb% ${url}$endpoint $query $config] + } } else { - set result [::rest::%verb% ${url}%endpoint% $query $config] + puts stderr "%commandname% not called because -FORCE is false" + set sanconfig [punk::netbox::system::obscured_config $config] + puts "url:${url}$endpoint query:'$query' verb:%verb% config:'$sanconfig'" + return } switch -exact -- $returntype { - showdict { + showpagedict { #return [punk::lib::showdict $result !@@results @@results/@*/@*.@*] + return [punk::lib::showdict $result %showpagedict%] + } + showpagedict2 { + #return [punk::lib::showdict $result @@results/@*/@*.@* !@@results] + return [punk::lib::showdict $result %showpagedict2%] + } + showdict { return [punk::lib::showdict $result %showdict%] } showdict2 { - #return [punk::lib::showdict $result @@results/@*/@*.@* !@@results] return [punk::lib::showdict $result %showdict2%] } + showlist { + return [punk::lib::showdict -roottype list $result] + } + showlistofdicts { + return [punk::lib::showdict $result %showlistofdicts%] + } + jsondump { + package require huddle::json + #pretty-print via huddle (inefficient review) + set h [huddle::json::json2huddle parse $result] + return [huddle::jsondump $h] + } + linelist { + set ret "" + foreach r $result { + append ret $r \n + } + return $ret + } default { - #dict or json - the counterintuitive 'result' field above sets this + #plain result: (list or dict) or json - the counterintuitive 'result' field set to raw above sets the rest resulting format to json return $result } } @@ -731,9 +817,13 @@ tcl::namespace::eval punk::netbox { } set _tenant_options { -tenant_group_id + -tenant_group_id__n -tenant_group + -tenant_group__n -tenant_id + -tenant_id__n -tenant + -tenant__n } set _region_options { -region_id @@ -741,17 +831,33 @@ tcl::namespace::eval punk::netbox { } set _site_options { -site_group_id + -site_group_id__n -site_group + -site_group__n -site_id + -site_id__n -site + -site__n } set _group_options { -group_id + -group_id__n -group + -group__n + } + set _contact_options { + -contact + -contact__n + -contact_role + -contact_role__n + -contact_group + -contact_group__n } set _role_options { -role_id + -role_id__n -role + -role__n } set _filter_string [list\ "ie \n Exact match\n(case-insensitive)"\ @@ -765,13 +871,35 @@ tcl::namespace::eval punk::netbox { "niew \n Does not end with\n (case-insensitive)"\ "empty \n Is empty/null"\ ] + set _filter_number [list\ + "n \n Not equal to"\ + "lte \n Less than or equal"\ + "lt \n Less than"\ + "gte \n Greater than or equal"\ + "gt \n Greater than"\ + ] set _CUSTOM_PARAMS { -CUSTOM_PARAM -type list -minsize 2 -maxsize 2 -multiple 1 -help\ "Specify a parameter not in this API e.g -CUSTOM_PARAM {mytag blah}" } - set _RETURN { - -RETURN -type string -choices {dict showdict showdict2 json} -choicelabels { + set _RETURN_PAGEDICT { + -RETURN -type string -choices {dict showpagedict showpagedict2 json jsondump} -choicelabels { + dict\ + " Tcl dictionary + (fastest)" + showpagedict\ + " human readable dict display + with same order as dict." + showpagedict2\ + " human readable dict display + results first, page metadata last." + } -help\ + "Options for returned data. + Note that showdict results are relatively slow, especially for large resultsets" + } + set _RETURN_DICT { + -RETURN -type string -choices {dict showdict showdict2 json jsondump} -choicelabels { dict\ " Tcl dictionary (fastest)" @@ -785,8 +913,34 @@ tcl::namespace::eval punk::netbox { "Options for returned data. Note that showdict results are relatively slow, especially for large resultsets" } + set _RETURN_LIST { + -RETURN -type string -choices {list linelist showlist json jsondump} -choicelabels { + list\ + " Tcl list + (fastest)" + linelist\ + " raw list with newline after each item" + showlist\ + " human readable list display" + } -help\ + "Options for returned data. + Note that showlist results are relatively slow, especially for large resultsets" + } + set _RETURN_LISTOFDICTS { + -RETURN -type string -choices {list linelist showlist json jsondump} -choicelabels { + list\ + " Tcl list + (fastest)" + linelist\ + " raw list with newline after each item" + showlistofdicts\ + " human readable display list of dicts" + } -help\ + "Options for returned data. + Note that showlist results are relatively slow, especially for large resultsets" + } set _RETURN_STATUS { - -RETURN -type string -default showdict2 -choices {dict showdict showdict2 json} -choicelabels { + -RETURN -type string -default showdict2 -choices {dict showdict showdict2 json jsondump} -choicelabels { dict\ " Tcl dictionary" showdict\ @@ -806,6 +960,11 @@ tcl::namespace::eval punk::netbox { set string_filter_help "Paired search filter for string:\n" append _string_filter_help [textblock::list_as_table -columns 4 -show_hseps 1 $_filter_string] + + #n, lte, lt, gte, gt + #e.g virtualization/virtual-machine vcpus, memory, disk + set number_filter_help "Paired search filter for number:\n" + append _number_filter_help [textblock::list_as_table -columns 3 -show_hseps 1 $_filter_number] } punk::args::define {*}[list\ @@ -920,14 +1079,104 @@ tcl::namespace::eval punk::netbox { } # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +tcl::namespace::eval punk::netbox::dcim { + namespace export {[a-z]*} + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::dcim::devices_list + @cmd -name punk::netbox::dcim::devices_list -help\ + "tenancy_tenants_list + GET request for endpoint /dcim/devices/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -name + -NAME_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_name_filter_help}} + -asset_tag -type string + -ASSET_TAG_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + -face -type string + -face__n -type string + -position -type integer + -POSITION_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -airflow -type string + -airflow__n -type string + -vc_position -type integer + -VC_POSITION_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -vc_priority -type integer + -VC_PRIORITY_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + }\ + [set ::punk::netbox::argdoc::_create_update_options]\ + { + -q -type string + -tag -type string + -tag__n -type string + }\ + [set ::punk::netbox::argdoc::_tenant_options]\ + [set ::punk::netbox::argdoc::_contact_options]\ + { + -local_context_data + -manufacturer_id + -manufacturer_id__n + -manufacturer + -manufacturer__n + -device_type_id + -device_type_id__n + -role_id + -role_id__n + -role + -role__n + -parent_device_id + -parent_device_id__n + -platform_id + -platform_id__n + -platform + -platform__n + }\ + [set ::punk::netbox::argdoc::_group_options]\ + [set ::punk::netbox::argdoc::_region_options]\ + [set ::punk::netbox::argdoc::_site_options]\ + { + -location_id -type integer + -location_id__n -type integer + -rack_id -type integer + -rack_id__n -type integer + -cluster_id -type integer + -cluster_id__n -type integer + -model -type string + -model__n -type string + -status -type string + -status__n -type string + -mac_address -type string + -MAC_ADDRESS_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + -serial -type string + -SERIAL_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + -virtual_chassis_id -type integer + -virtual_chassis_id__n -type integer + }\ + [set ::punk::netbox::argdoc::_page_options]\ + [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ + { + @values -min 0 -max 0 + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::dcim::devices_list api/dcim/devices/ -verb get -body none +} tcl::namespace::eval punk::netbox::ipam { namespace export {[a-z]*} lappend PUNKARGS [list\ { @dynamic - @id -id ::punk::netbox::ipam::vrfs - @cmd -name punk::netbox::ipam::vrfs -help\ + @id -id ::punk::netbox::ipam::vrfs_list + @cmd -name punk::netbox::ipam::vrfs_list -help\ "ipam_vrfs_list GET request for endpoint /ipam/vrfs/" @leaders -min 1 -max 1 @@ -939,6 +1188,7 @@ tcl::namespace::eval punk::netbox::ipam { -choices {${[punk::netbox::api_context_names]}} @opts -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -name -NAME_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_name_filter_help}} -rd -type string -help\ @@ -964,19 +1214,42 @@ tcl::namespace::eval punk::netbox::ipam { }\ [set ::punk::netbox::argdoc::_page_options]\ [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ - [set ::punk::netbox::argdoc::_RETURN]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ { @values -min 0 -max 0 }] - ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::vrfs api/ipam/vrfs/ -verb get -body none + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::vrfs_list api/ipam/vrfs/ -verb get -body none + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::ipam::vrfs_read + @cmd -name punk::netbox::ipam::vrfs_read -help\ + "ipam_vrfs_list + GET request for endpoint /ipam/vrfs/{id}" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + id -type integer -help\ + "A unique integer value identifying this VRF" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::vrfs_read api/ipam/vrfs/{id}/ -verb get -body none punk::args::define {*}[list\ { @dynamic - @id -id ::punk::netbox::ipam::prefixes - @cmd -name punk::netbox::ipam::prefixes -help\ + @id -id ::punk::netbox::ipam::prefixes_list + @cmd -name punk::netbox::ipam::prefixes_list -help\ "ipam_prefixes_list GET request for endpoint /ipam/prefixes/" @leaders -min 1 -max 1 @@ -988,6 +1261,7 @@ tcl::namespace::eval punk::netbox::ipam { -choices {${[punk::netbox::api_context_names]}} @opts -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -is_pool -mark_utilized -description -type string -help "Exact Match (case sensitive)" @@ -995,7 +1269,8 @@ tcl::namespace::eval punk::netbox::ipam { }\ [set ::punk::netbox::argdoc::_create_update_options]\ { - -q + -q -type string -help\ + "Query prefixes by substring" -tag }\ [set ::punk::netbox::argdoc::_tenant_options]\ @@ -1010,31 +1285,154 @@ tcl::namespace::eval punk::netbox::ipam { -within_include -contains -depth + -DEPTH_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -children + -CHILDREN_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -mask_length -mask_length__gte -mask_length__lte + -vlan_id -type integer + -vlan_id__n -type integer + -vlan_vid -type integer + -VLAN_VID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -vrf_id -vrf - -role_id - -role -status -available_on_device -available_on_virtualmachine }\ [set ::punk::netbox::argdoc::_page_options]\ [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ - [set ::punk::netbox::argdoc::_RETURN]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ { @values -min 0 -max 0 }] - ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes api/ipam/prefixes/ -verb get -body none + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes_list api/ipam/prefixes/ -verb get -body none + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::prefixes_create + @cmd -name punk::netbox::ipam::prefixes_create -help\ + "ipam_prefixes_create + POST request for endpoint /ipam/prefixes/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + body -type string -help\ + "JSON string" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes_create api/ipam/prefixes/{id}/ -verb post -body required + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::prefixes_read + @cmd -name punk::netbox::ipam::prefixes_read -help\ + "ipam_prefixes_read + GET request for endpoint /ipam/prefixes/{id}/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + id -type integer -help\ + "A unique integer value identifying this prefix" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes_read api/ipam/prefixes/{id}/ -verb get -body none + + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::prefixes_available-ips_list + @cmd -name punk::netbox::ipam::prefixes_available-ips_list -help\ + "ipam_prefixes_available-ips_list + GET request for endpoint /ipam/prefixes/{id}/available-ips/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_page_options]\ + [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ + [set ::punk::netbox::argdoc::_RETURN_LISTOFDICTS]\ + { + @values -min 1 -max 1 + id -type integer -help\ + "A unique integer value identifying this prefix" + }\ + ] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes_available-ips_list api/ipam/prefixes/{id}/available-ips/ -verb get -body none punk::args::define {*}[list\ { @dynamic - @id -id ::punk::netbox::ipam::ip-addresses - @cmd -name punk::netbox::ipam::ip-addresses -help\ + @id -id ::punk::netbox::ipam::prefixes_available-ips_create + @cmd -name punk::netbox::ipam::prefixes_available-ips_create -help\ + "ipam_prefixes_available-ips_create + POST request for endpoint /ipam/prefixes/{id}/available-ips/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 2 + id -type integer -help\ + "A unique integer value identifying this prefix" + body -type string -default "" -help\ + { + If empty create a single IP with default values. + (next available IP in prefix) + + Create 2 IPs: + [ + {"description": "ip1"}, + {"description": "ip2"} + ] + NOTE: This always uses next available IPs. + To create a specific IP, use api/ipam/ip-addresses endpoint. + + The returned json is just an object if one address created, + but a list if multiple. :/ + + } + }\ + ] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::prefixes_available-ips_create api/ipam/prefixes/{id}/available-ips/ -verb post -body required + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::ip-addresses_list + @cmd -name punk::netbox::ipam::ip-addresses_list -help\ "ipam_ip-addresses_list GET request for endpoint /ipam/ip-addresses/" @leaders -min 1 -max 1 @@ -1046,6 +1444,7 @@ tcl::namespace::eval punk::netbox::ipam { -choices {${[punk::netbox::api_context_names]}} @opts -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} -dns_name -DNS_NAME_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} -description -type string -help "Exact Match (case sensitive)" @@ -1087,13 +1486,359 @@ tcl::namespace::eval punk::netbox::ipam { }\ [set ::punk::netbox::argdoc::_page_options]\ [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ - [set ::punk::netbox::argdoc::_RETURN]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ { @values -min 0 -max 0 }] - ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::ip-addresses api/ipam/ip-addresses/ -verb get -body none + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::ip-addresses_list api/ipam/ip-addresses/ -verb get -body none + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::ip-addresses_read + @cmd -name punk::netbox::ipam::ip-addresses_read -help\ + "ipam_ip-addresses_read + GET request for endpoint /ipam/ip-addresses/{id}/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + id -type integer + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::ip-addresses_read api/ipam/ip-addresses/{id}/ -verb get -body none + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::ip-addresses_create + @cmd -name punk::netbox::ipam::ip-addresses_create -help\ + "ipam_ip-addresses_create + POST request for endpoint /ipam/ip-addresses/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + body -type string -help\ + {JSON string + Example: + { + "address": "string", + "vrf": 0, + "tenant": 0, + "status": "active", + "role": "loopback", + "assigned_object_type": "string", + "assigned_object_id": 0, + "nat_inside": 0, + "dns_name": "string", + "description": "string", + "tags": [ + { + "name": "string", + "slug": "string", + "color": "string" + } + ], + "custom_fields": {} + } + Required: address (IPv4 or IPV6 address with mask) + } + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::ip-addresses_create api/ipam/ip-addresses/ -verb post -body required + + punk::args::define {*}[list\ + { + @dynamic + @id -id ::punk::netbox::ipam::ip-addresses_bulk_partial_update + @cmd -name punk::netbox::ipam::ip-addresses_bulk_partial_update -help\ + "ipam_ip-addresses_bulk_partical_update + PATCH request for endpoint /ipam/ip-addresses/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + body -type string -help\ + {JSON string + model: + { + "address": "string", + "vrf": 0, + "tenant": 0, + "status": "active", + "role": "loopback", + "assigned_object_type": "string", + "assigned_object_id": 0, + "nat_inside": 0, + "dns_name": "string", + "description": "string", + "tags": [ + { + "name": "string", + "slug": "string", + "color": "string" + } + ], + "custom_fields": {} + } + required: address + } + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::ipam::ip-addresses_bulk_partial_update api/ipam/ip-addresses/ -verb patch -body required + +} +tcl::namespace::eval punk::netbox::tenancy { + namespace export {[a-z]*} + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::tenancy::tenants_list + @cmd -name punk::netbox::tenancy::tenants_list -help\ + "tenancy_tenants_list + GET request for endpoint /tenancy/tenants/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -name + -NAME_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_name_filter_help}} + -slug -type string + -SLUG_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + -description -type string + -DESCRIPTION_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + }\ + [set ::punk::netbox::argdoc::_create_update_options]\ + { + -q -type string + -tag -type string + -tag__n -type string + }\ + [set ::punk::netbox::argdoc::_tenant_options]\ + [set ::punk::netbox::argdoc::_contact_options]\ + { + }\ + { + }\ + [set ::punk::netbox::argdoc::_group_options]\ + { + }\ + [set ::punk::netbox::argdoc::_page_options]\ + [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ + { + @values -min 0 -max 0 + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::tenancy::tenants_list api/tenancy/tenants/ -verb get -body none +} + +tcl::namespace::eval punk::netbox::virtualization { + namespace export {[a-z]*} + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::virtualization::virtual-machines_list + @cmd -name punk::netbox::virtualization::virtual-machines_list -help\ + "virtualization_virtual-machines_list + GET request for endpoint /virtualization/virtual-machines/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + -id -type integer + -ID_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -name + -NAME_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_name_filter_help}} + -cluster -type string + -cluster_n -type string + -vcpus -type integer + -VCPUS_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -memory -type integer -help\ + "Whole number" + -MEMORY_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + -disk -type integer + -DISK_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_number_filter_help}} + }\ + [set ::punk::netbox::argdoc::_create_update_options]\ + { + -q + -tag + }\ + [set ::punk::netbox::argdoc::_tenant_options]\ + [set ::punk::netbox::argdoc::_contact_options]\ + { + -local_context_data + -status + -status_n + -cluster_group_id + -cluster_group_id__n + -cluster_group + -cluster_group__n + -cluster_type_id + -cluster_type_id__n + -cluster_type + -cluster_type__n + -cluster_id + -cluster_id__n + }\ + [set ::punk::netbox::argdoc::_region_options]\ + [set ::punk::netbox::argdoc::_site_options]\ + { + -platform + -platform__n + -mac_address + -MAC_ADDRESS_FILTER -type list -minsize 2 -maxsize 2 -multiple 1 -help {${$::punk::netbox::argdoc::_string_filter_help}} + -has_primary_ip + }\ + [set ::punk::netbox::argdoc::_group_options]\ + [set ::punk::netbox::argdoc::_role_options]\ + { + }\ + [set ::punk::netbox::argdoc::_page_options]\ + [set ::punk::netbox::argdoc::_CUSTOM_PARAMS]\ + [set ::punk::netbox::argdoc::_RETURN_PAGEDICT]\ + { + @values -min 0 -max 0 + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::virtualization::virtual-machines_list api/virtualization/virtual-machines/ -verb get -body none + + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::virtualization::virtual-machines_create + @cmd -name punk::netbox::virtualization::virtual-machines_create -help\ + "virtualization_virtual-machines_create + GET request for endpoint /virtualization/virtual-machines/" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 2 -max 2 + id -type integer -help\ + "A unique integer value identifying this virtual machine" + body -type string -help\ + "JSON string" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::virtualization::virtual-machines_create api/virtualization/virtual-machines/ -verb post -body required + + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::virtualization::virtual-machines_delete + @cmd -name punk::netbox::virtualization::virtual-machines_delete -help\ + "virtualization_virtual-machines_delete + DELETE request for endpoint /virtualization/virtual-machines/ + HTTP code: 204 + " + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + -FORCE -default 0 -type boolean -help\ + "Set to true to BULK delete all items at this endpoint" + }\ + { + @values -min 0 -max 0 + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::virtualization::virtual-machines_delete api/virtualization/virtual-machines/ -verb delete -body none + + + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::virtualization::virtual-machines_read + @cmd -name punk::netbox::virtualization::virtual-machines_read -help\ + "virtualization_virtual-machines_read + GET request for endpoint /virtualization/virtual-machines/{id}" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 1 -max 1 + id -type integer -help\ + "A unique integer value identifying this virtual machine" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::virtualization::virtual-machines_read api/virtualization/virtual-machines/{id}/ -verb get -body none + + lappend PUNKARGS [list\ + { + @dynamic + @id -id ::punk::netbox::virtualization::virtual-machines_update + @cmd -name punk::netbox::virtualization::virtual-machines_update -help\ + "virtualization_virtual-machines_update + PUT request for endpoint /virtualization/virtual-machines/{id}" + @leaders -min 1 -max 1 + apicontextid -help\ + "The name of the stored api context to use. + A contextid can be created in-memory using + api_context_create, or loaded from a .toml + file using api_context_load."\ + -choices {${[punk::netbox::api_context_names]}} + @opts + }\ + [set ::punk::netbox::argdoc::_RETURN_DICT]\ + { + @values -min 2 -max 2 + id -type integer -help\ + "A unique integer value identifying this virtual machine" + body -type string -help\ + "JSON string" + }] + ::punk::netbox::system::make_rest_func ::punk::netbox::virtualization::virtual-machines_update api/virtualization/virtual-machines/{id}/ -verb put -body required } + + # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # Secondary API namespace # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ @@ -1186,6 +1931,7 @@ tcl::namespace::eval punk::netbox { punk::args::lib::tstr -return string { netbox /status/ endpoint beginnings of /ipam/ endpoints + beginnings of /virtualization/ endpoints } } # ------------------------------------------------------------- @@ -1222,7 +1968,12 @@ tcl::namespace::eval punk::netbox { # variable PUNKARGS_aliases namespace eval ::punk::args::register { #use fully qualified so 8.6 doesn't find existing var in global namespace - lappend ::punk::args::register::NAMESPACES ::punk::netbox ::punk::netbox::ipam + lappend ::punk::args::register::NAMESPACES\ + ::punk::netbox\ + ::punk::netbox::dcim\ + ::punk::netbox::ipam\ + ::punk::netbox::tenancy\ + ::punk::netbox::virtualization } # ----------------------------------------------------------------------------- diff --git a/src/vfs/_vfscommon.vfs/modules/punk/netbox/man-0.1.0.tm b/src/vfs/_vfscommon.vfs/modules/punk/netbox/man-0.1.0.tm new file mode 100644 index 00000000..b4b73b76 --- /dev/null +++ b/src/vfs/_vfscommon.vfs/modules/punk/netbox/man-0.1.0.tm @@ -0,0 +1,601 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from -buildversion.txt +# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm +# +# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. +# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# (C) 2025 +# +# @@ Meta Begin +# Application punk::netbox::man 0.1.0 +# Meta platform tcl +# Meta license +# @@ Meta End + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# doctools header +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#*** !doctools +#[manpage_begin shellspy_module_punk::netbox::man 0 0.1.0] +#[copyright "2025"] +#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] +#[moddesc {-}] [comment {-- Description at end of page heading --}] +#[require punk::netbox::man] +#[keywords module] +#[description] +#[para] - + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[section Overview] +#[para] overview of punk::netbox::man +#[subsection Concepts] +#[para] - + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[subsection dependencies] +#[para] packages used by punk::netbox::man +#[list_begin itemized] + +package require Tcl 8.6- +package require punk::netbox +package require uri +package require rest + +#*** !doctools +#[item] [package {Tcl 8.6}] +#[item] [package {punk::netbox}] + +# #package require frobz +# #*** !doctools +# #[item] [package {frobz}] + +#*** !doctools +#[list_end] + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[section API] + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# oo::class namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#tcl::namespace::eval punk::netbox::man::class { + #*** !doctools + #[subsection {Namespace punk::netbox::man::class}] + #[para] class definitions + #if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} { + #*** !doctools + #[list_begin enumerated] + + # oo::class create interface_sample1 { + # #*** !doctools + # #[enum] CLASS [class interface_sample1] + # #[list_begin definitions] + + # method test {arg1} { + # #*** !doctools + # #[call class::interface_sample1 [method test] [arg arg1]] + # #[para] test method + # puts "test: $arg1" + # } + + # #*** !doctools + # #[list_end] [comment {-- end definitions interface_sample1}] + # } + + #*** !doctools + #[list_end] [comment {--- end class enumeration ---}] + #} +#} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +tcl::namespace::eval punk::netbox::man { + namespace export {[a-z]*} + variable PUNKARGS + + #review + ? + proc uri_part_decode {uripart} { + set specialMap {"[" "%5B" "]" "%5D" + " "} + set seqRE {%([0-9a-fA-F]{2})} + set replacement {[format "%c" [scan "\1" "%2x"]]} + set modstr [regsub -all $seqRE [string map $specialMap $uripart] $replacement] + return [encoding convertfrom utf-8 [subst -nobackslash -novariable $modstr]] + } + + proc uri_get_querystring_as_keyval_list {uri} { + set parts [uri::split $uri] + set query ?[dict get $parts query] + set raw_plist [rest::parameters $query] ;#not a dict - can have repeated params (important for _FILTER methods) + return [lmap v $raw_plist {uri_part_decode $v}] + } +} + + +tcl::namespace::eval punk::netbox::man::prefixes { + # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + # Base namespace + # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + #*** !doctools + #[subsection {Namespace punk::netbox::man}] + #[para] Core API functions for punk::netbox::man + #[list_begin definitions] + + namespace export {[a-z]*} + namespace ensemble create + + variable PUNKARGS + lappend PUNKARGS [::list\ + [punk::args::resolved_def -antiglobs {apicontextid @leaders @values -RETURN} -override {@id {-id "::punk::netbox::man::prefixes list"}} ::punk::netbox::ipam::prefixes_list]\ + {-RETURN -default table -choices {table tableobject list}}\ + {@values -min 0 -max 0}\ + ] + + #caution: must use ::list to avoid loop + proc list {args} { + set argd [punk::args::parse $args withid "::punk::netbox::man::prefixes list"] + set token tclread ;#todo + + set next "" + set requests_allowed 1000 ;#review + set resultlist [::list] + set opts [dict get $argd opts] + set vals [dict get $argd values] + set multis [dict get $argd multis] + set outer_return [dict get $opts -RETURN] + set opts [dict remove $opts -RETURN] ;#opts from punk::args::parse is a dict (no dup keys) - can use 'dict remove' safely + #we can't just pass through 'multi' opts even if only one was supplied - list level is wrong + set nextopts [::list] + dict for {opt val} $opts { + if {$opt ni $multis} { + lappend nextopts $opt $val + } else { + foreach v $val { + lappend nextopts $opt $v + } + } + } + #Now opts is a list with possible repeated options! (for flags that have -multiple true) + + while {$next ne "null"} { + if {$next ni [::list "" null]} { + set plist [punk::netbox::man::uri_get_querystring_as_keyval_list $next] + #don't use any dict write operations on plist/nextopts - can destroy dup keys + set p_offset [lsearch -stride 2 $plist offset] ;#only search in 'key' positions - for -offset we are only expecting/allowing a single entry + if {$p_offset != -1} { + lappend nextopts -offset [lindex $plist $p_offset+1] + } + set p_limit [lsearch -stride 2 $plist limit] + if {$p_limit != -1} { + lappend nextopts -limit [lindex $plist $p_limit+1] + } + } + puts "-->next:$next nextopts:$nextopts vals:$vals" + set resultd [punk::netbox::ipam::prefixes_list $token {*}$nextopts -RETURN dict {*}$vals] + set next [dict get $resultd next] + set batch [dict get $resultd results] + lappend resultlist {*}$batch + incr requests_allowed -1 + if {$requests_allowed < 1} {break} + } + if {$outer_return in {table tableobject}} { + package require textblock + set t [textblock::list_as_table -return tableobject -colheaders {id prefix family vrf tenant children status vlan description _depth}] + foreach pfx $resultlist { + if {[dict exists $pfx tenant id]} { + set tenant "[dict get $pfx tenant id]: [dict get $pfx tenant slug]" + } else { + set tenant [dict get $pfx tenant] ;#probably null + } + if {[dict exists $pfx vlan id]} { + set vlan "[dict get $pfx vlan id]: [dict get $pfx vlan display]" + } else { + set vlan [dict get $pfx vlan] ;#probably null + } + set r [::list\ + [dict get $pfx id]\ + [dict get $pfx display]\ + [dict get $pfx family label]\ + [dict get $pfx vrf id]\ + $tenant\ + [dict get $pfx children]\ + [dict get $pfx status value]\ + $vlan\ + [dict get $pfx description]\ + [dict get $pfx _depth]\ + ] + $t add_row $r + } + } + switch -- $outer_return { + table { + set result [$t print] + $t destroy + return $result + } + tableobject { + return $t + } + } + return $resultlist + #return [showdict $resultd] + } + + + #lappend PUNKARGS [::list\ + # [punk::args::resolved_def -antiglobs {apicontextid @leaders @values -RETURN} -override {@id {-id "::punk::netbox::man::prefixes available-ips_list"}} ::punk::netbox::ipam::prefixes_available-ips_list]\ + # {-RETURN -default table -choices {table tableobject list}} + # ] + lappend PUNKARGS [::list\ + [punk::args::resolved_def\ + -antiglobs {apicontextid @leaders -offset}\ + -override {\ + @id {-id "::punk::netbox::man::prefixes available-ips_list"}\ + -limit {-default 254 -help "Maximum number of entries to return"}\ + -RETURN {-default table -choices {table tableobject list linelist}}\ + @values {-min 1 -max 1}\ + }\ + ::punk::netbox::ipam::prefixes_available-ips_list\ + ]\ + ] + + proc available-ips_list {args} { + set argd [punk::args::parse $args withid "::punk::netbox::man::prefixes available-ips_list"] + set token tclread ;#todo + + set next "" + set requests_allowed 1000 ;#review + set resultlist [::list] + set opts [dict get $argd opts] + set valuedict [dict get $argd values] + set vals [dict values $valuedict] ;#we don't need the keys to pass on to next func + set multis [dict get $argd multis] + set outer_return [dict get $opts -RETURN] + set opts [dict remove $opts -RETURN] ;#opts from punk::args::parse is a dict (no dup keys) - can use 'dict remove' safely + #we can't just pass through 'multi' opts even if only one was supplied - list level is wrong + set nextopts [::list] + dict for {opt val} $opts { + if {$opt ni $multis} { + lappend nextopts $opt $val + } else { + foreach v $val { + lappend nextopts $opt $v + } + } + } + #Now opts is a list with possible repeated options! (for flags that have -multiple true) + + #No paging available at endpoint ipam/prefixes/available-ips - but we can still use limit (but offset doesn't seem to work + set resultlist [punk::netbox::ipam::prefixes_available-ips_list $token {*}$nextopts -RETURN list {*}$vals] + + if {$outer_return in {table tableobject}} { + package require textblock + set t [textblock::list_as_table -return tableobject -colheaders {address family vrf}] + foreach ip $resultlist { + set r [::list\ + [dict get $ip address]\ + [dict get $ip family]\ + "[dict get $ip vrf id]: [dict get $ip vrf name]"\ + ] + $t add_row $r + } + } + switch -- $outer_return { + table { + set result [$t print] + $t destroy + return $result + } + tableobject { + return $t + } + linelist { + set ret "" + foreach r $resultlist { + append ret $r \n + } + return $ret + } + jsondump { + #todo + package require huddle::json + #pretty-print via huddle (inefficient review) + set h [huddle::json::json2huddle parse $result] + return [huddle::jsondump $h] + } + default { + return $resultlist + } + } + #return [showdict $resultd] + } + + + + #*** !doctools + #[list_end] [comment {--- end definitions namespace punk::netbox::man ---}] +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + +tcl::namespace::eval punk::netbox::man::ip-addresses { + namespace export {[a-z]*} + namespace ensemble create + variable PUNKARGS + + lappend PUNKARGS [::list\ + [punk::args::resolved_def -antiglobs {apicontextid @leaders @values -RETURN} -override {@id {-id "::punk::netbox::man::ip-addresses list"}} ::punk::netbox::ipam::ip-addresses_list]\ + {-RETURN -default table -choices {table tableobject list}}\ + {@values -min 0 -max 0}\ + ] + + #caution: must use ::list to avoid loop + proc list {args} { + set argd [punk::args::parse $args withid "::punk::netbox::man::ip-addresses list"] + set token tclread ;#todo + + set next "" + set requests_allowed 1000 ;#review + set resultlist [::list] + set opts [dict get $argd opts] + set vals [dict get $argd values] + set multis [dict get $argd multis] + set outer_return [dict get $opts -RETURN] + set opts [dict remove $opts -RETURN] ;#opts from punk::args::parse is a dict (no dup keys) - can use 'dict remove' safely + #we can't just pass through 'multi' opts even if only one was supplied - list level is wrong + set nextopts [::list] + dict for {opt val} $opts { + if {$opt ni $multis} { + lappend nextopts $opt $val + } else { + foreach v $val { + lappend nextopts $opt $v + } + } + } + #Now opts is a list with possible repeated options! (for flags that have -multiple true) + + while {$next ne "null"} { + if {$next ni [::list "" null]} { + set plist [punk::netbox::man::uri_get_querystring_as_keyval_list $next] + #don't use any dict write operations on plist/nextopts - can destroy dup keys + set p_offset [lsearch -stride 2 $plist offset] ;#only search in 'key' positions - for -offset we are only expecting/allowing a single entry + if {$p_offset != -1} { + lappend nextopts -offset [lindex $plist $p_offset+1] + } + set p_limit [lsearch -stride 2 $plist limit] + if {$p_limit != -1} { + lappend nextopts -limit [lindex $plist $p_limit+1] + } + } + puts "-->next:$next nextopts:$nextopts vals:$vals" + set resultd [punk::netbox::ipam::ip-addresses_list $token {*}$nextopts -RETURN dict {*}$vals] + set next [dict get $resultd next] + set batch [dict get $resultd results] + lappend resultlist {*}$batch + incr requests_allowed -1 + if {$requests_allowed < 1} {break} + } + if {$outer_return in {table tableobject}} { + package require textblock + set t [textblock::list_as_table -return tableobject -colheaders {id address family vrf_id tenant status assigned_object_type deviceinfo dns_name description}] + foreach ip $resultlist { + if {[dict exists $ip tenant id]} { + set tenant "[dict get $ip tenant id]: [dict get $ip tenant slug]" + } else { + set tenant [dict get $ip tenant] ;#probably null + } + switch -- [dict get $ip assigned_object_type] { + dcim.interface { + set device_id [dict get $ip assigned_object device id] + set device_display [dict get $ip assigned_object device display] + set deviceinfo "$device_id: $device_display" + } + virtualization.vminterface { + set deviceinfo "vm" + } + default { + set deviceinfo - + } + } + set r [::list\ + [dict get $ip id]\ + [dict get $ip address]\ + [dict get $ip family label]\ + [dict get $ip vrf id]\ + $tenant\ + [dict get $ip status value]\ + [dict get $ip assigned_object_type]\ + $deviceinfo\ + [dict get $ip dns_name]\ + [dict get $ip description]\ + ] + $t add_row $r + } + } + switch -- $outer_return { + table { + set result [$t print] + $t destroy + return $result + } + tableobject { + return $t + } + } + return $resultlist + #return [showdict $resultd] + } + + +} + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# Secondary API namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +tcl::namespace::eval punk::netbox::man::lib { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + tcl::namespace::path [tcl::namespace::parent] + #*** !doctools + #[subsection {Namespace punk::netbox::man::lib}] + #[para] Secondary functions that are part of the API + #[list_begin definitions] + + #proc utility1 {p1 args} { + # #*** !doctools + # #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]] + # #[para]Description of utility1 + # return 1 + #} + + + + #*** !doctools + #[list_end] [comment {--- end definitions namespace punk::netbox::man::lib ---}] +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#*** !doctools +#[section Internal] +#tcl::namespace::eval punk::netbox::man::system { + #*** !doctools + #[subsection {Namespace punk::netbox::man::system}] + #[para] Internal functions that are not part of the API + + + +#} + + +# == === === === === === === === === === === === === === === +# Sample 'about' function with punk::args documentation +# == === === === === === === === === === === === === === === +tcl::namespace::eval punk::netbox::man { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + variable PUNKARGS + variable PUNKARGS_aliases + + lappend PUNKARGS [list { + @id -id "(package)punk::netbox::man" + @package -name "punk::netbox::man" -help\ + "Package + Description" + }] + + namespace eval argdoc { + #namespace for custom argument documentation + proc package_name {} { + return punk::netbox::man + } + proc about_topics {} { + #info commands results are returned in an arbitrary order (like array keys) + set topic_funs [info commands [namespace current]::get_topic_*] + set about_topics [list] + foreach f $topic_funs { + set tail [namespace tail $f] + lappend about_topics [string range $tail [string length get_topic_] end] + } + #Adjust this function or 'default_topics' if a different order is required + return [lsort $about_topics] + } + proc default_topics {} {return [list Description *]} + + # ------------------------------------------------------------- + # get_topic_ functions add more to auto-include in about topics + # ------------------------------------------------------------- + proc get_topic_Description {} { + punk::args::lib::tstr [string trim { + package punk::netbox::man + A management wrapper over the punk::netbox + REST API. + } \n] + } + proc get_topic_License {} { + return "" + } + proc get_topic_Version {} { + return "$::punk::netbox::man::version" + } + proc get_topic_Contributors {} { + set authors { Julian Noble} + set contributors "" + foreach a $authors { + append contributors $a \n + } + if {[string index $contributors end] eq "\n"} { + set contributors [string range $contributors 0 end-1] + } + return $contributors + } + proc get_topic_custom-topic {} { + punk::args::lib::tstr -return string { + todo - next available ip-address from prefix + } + } + # ------------------------------------------------------------- + } + + # we re-use the argument definition from punk::args::standard_about and override some items + set overrides [dict create] + dict set overrides @id -id "::punk::netbox::man::about" + dict set overrides @cmd -name "punk::netbox::man::about" + dict set overrides @cmd -help [string trim [punk::args::lib::tstr { + About punk::netbox::man + }] \n] + dict set overrides topic -choices [list {*}[punk::netbox::man::argdoc::about_topics] *] + dict set overrides topic -choicerestricted 1 + dict set overrides topic -default [punk::netbox::man::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict + set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *] + lappend PUNKARGS [list $newdef] + proc about {args} { + package require punk::args + #standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on + set argd [punk::args::parse $args withid ::punk::netbox::man::about] + lassign [dict values $argd] _leaders opts values _received + punk::args::package::standard_about -package_about_namespace ::punk::netbox::man::argdoc {*}$opts {*}[dict get $values topic] + } +} +# end of sample 'about' function +# == === === === === === === === === === === === === === === + + +# ----------------------------------------------------------------------------- +# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked +# ----------------------------------------------------------------------------- +# variable PUNKARGS +# variable PUNKARGS_aliases +namespace eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES\ + ::punk::netbox::man\ + ::punk::netbox::man::prefixes\ + ::punk::netbox::man::ip-addresses + +} +# ----------------------------------------------------------------------------- + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Ready +package provide punk::netbox::man [tcl::namespace::eval punk::netbox::man { + variable pkg punk::netbox::man + variable version + set version 0.1.0 +}] +return + +#*** !doctools +#[manpage_end] +