Gavin Whitson, 6/12/2024
A couple of weeks ago my bosses approached me wanting to create a small report that pulls from the API provided by NSX . The university is primarily a Windows shop so PowerShell is frequently the tool of choice as it is Windows native and has great compatibility out of the box across the majority of our servers. I was estatic to be told that I could use any language of my choice for this project.
The API provided is a REST API and using some additional tooling that was provided by another team that was written in python, I was able to setup the report. Some time after that, they had another idea for something using the API. When the Applications team for the university runs updates on a system they manage, they sometimes have to have it stop participating in a load balanced pool provided by NSX. Toggling this simple setting is a surprisingly tedious process requiring the perfect order of opening and closing different menus. They were wondering if I could write a small program to toggle the participation of these servers in their pool. Once again, I was able to use whatever language I wanted for this project.
With an established base from the report I had previously made, I decided that python would likely be the best tool for the job. Scrolling throught the documentaion more, then formatting the proper API call, I had the basic functionality done the next day and had a basic, one-use, script that could toggle these servers. The issue came when I was unhappy with the fact that you would have to run the script again to toggle another server or to change the same one back to it's previous state. Having worked on PythonDND for quite some time, I was already familiar with the TKinter library and was able to get a basic GUI working for it. Once again I was unsatisfied. A small issue was that I could not use py2exe to create it as a windowless executable and so the shell window would pop up in the background. That bothered me but it wasn't the biggest deal. My main problem was that it did not update the content it was rendering as it was changed on the website. If some context was changed before you make another selection, you could potentially put a server in an unintended state.
I believed that I could do a simple asynchronous loop that queried the same API and compared the result against the content being rendered and replace it when it was different. This worked properly, however the application was so sluggish as python was not doing the best job of handling the subroutine. With the initial GUI designed, there would be some input lag where sometimes clicking on a button would do nothing for roughly a second. I figured that I could take out both of my problems and implement a TUI interface for this for the user to interact with through the keyboard. Banging away on my keyboard once again I crafted a simple TUI using raw python and no library (quite the mistake) and hand crafted a TUI app that did not do live updating. This version worked great, I was happy with the overall performance of it and although it was definitely a little janky, it was mine. Trying to add the same asynchronous loop, once again I was destroying my programs performance.
When previously working on the report, I had first made a version with Go. This had it's own issues where it wouldn't compile on Windows as it was being detected as malware (it was making some HTTP request and then dumping the result to a file...). Another issue with this was that you had to input the credentials by hand. An obvious downside for a report we are supposed to get automatically. That is when I learned about the python module available for fetching credentials from the IT departments password manager. That caused me to switch to python and the rest of that project was a breeze. My current issues were enough for me to go back to Go, and implement my app with a language other than python.
I will admit, part of my decision to jump the python ship and swim over to Golang was because I have been wanting to write more and more Go. I find the language a easy to work with and I enjoy the syntax and structure that it uses. I knew of a TUI library and I had every piece of the puzzle to make my app with Go, aside from the a way to fetch credentials. Although for this case, it may be reasonable to have the user input their own credentials, I was provided with an API key to use and wanted to craft a module to go and get it from our password manager. I then, had an issue to solve.
Parsing JSON in Go is a pain compared to python. As described by the on 'go.dev blog JSON and Go', encoding and decoding JSON with Go required using a structure to access the data. This can be a bit of pain manually writing out every field required for the API interaction. Luckily, there are a multitude of tools available for easily translating a json string to Go structure. For this project speficically, I used https://transform.tools/json-to-go . I did modify the output from this site somewhat. The JSON response from NSX's API contains three levels of nesting. The structures I created were:
package nsxAPI type Response struct { Results []LBPool `json:"results"` ResultCount int `json:"result_count"` SortBy string `json:"sort_by"` SortAscending bool `json:"sort_ascending"` } type LBPool struct { Algorithm string `json:"algorithm"` Members []Server `json:"members"` ActiveMonitorPaths []string `json:"active_monitor_paths"` SnatTranslation struct { Type string `json:"type"` } `json:"snat_translation"` PassiveMonitorPath string `json:"passive_monitor_path"` TCPMultiplexingEnabled bool `json:"tcp_multiplexing_enabled"` TCPMultiplexingNumber int `json:"tcp_multiplexing_number"` MinActiveMembers int `json:"min_active_members"` ResourceType string `json:"resource_type"` ID string `json:"id"` DisplayName string `json:"display_name"` Path string `json:"path"` RelativePath string `json:"relative_path"` ParentPath string `json:"parent_path"` RemotePath string `json:"remote_path"` UniqueID string `json:"unique_id"` RealizationID string `json:"realization_id"` OwnerID string `json:"owner_id"` MarkedForDelete bool `json:"marked_for_delete"` Overridden bool `json:"overridden"` CreateTime int64 `json:"_create_time"` CreateUser string `json:"_create_user"` LastModifiedTime int64 `json:"_last_modified_time"` LastModifiedUser string `json:"_last_modified_user"` SystemOwned bool `json:"_system_owned"` Protection string `json:"_protection"` Revision int `json:"_revision"` } type Server struct { DisplayName string `json:"display_name"` IPAddress string `json:"ip_address"` AdminState string `json:"admin_state"` BackupMember bool `json:"backup_member"` Weight int `json:"weight"` }
As the overall application needs to affect the data of a specific server in some pool, abstracting out these different types with Response which contains a list of LBPool objects which themselves contain a list of Server objects that will be modified. This allows for easy iteration over each pool and then each server in a selected pool. I will not go over the specifics of the implementation with bubbletea in this blog past saying that it was an incredibly simple and streamlined library that I plan to use again. I plan to write another blog post about this library in specific. (I will update this with a link when available)
With all of the data accessible, bubbletea made it easy as it essentially lets you print format strings for the render function. Following the design of my python programs, I setup an app class that held a little bit of state information and some controls to toggle between the different menus needed. Once again, I had a version of my app that back to my baseline of: works well but doesn't update itself when the data is changed on the server. A key feature of using Go is the 'go' keyword available that allows you to run a function on a separate thread easily. I started to build out my async loop to verify and update the information and was joyed when it actually worked exactly as I intended.
I thouroughly enjoyed this project. There were a ton of wins along with a ton of tiny failures that was the perfect combination to keep me actively pushing to find a better soltion. It is also thanks to my supervisors for allowing to stretch my legs so to speak and expirment with different solutions in order to find something that I was truly happy with.
There is a little more work that I would like to do for this project. First and foremost, I am working on cleaning any sensitive info related to the Angelo State environment and making a generalized version that can be open source and usable by other people. Along with that, I am planning on implenting a line that shows the current status of the server, however this is another API call that I have to build out fuctions for. Along with this, I got the other department setup with the first build of the app and will have to fix any issues that they find with it.