From e27afc6e321df1e02ba2c4a0845764ec62a43c86 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Fri, 30 Jul 2021 12:04:44 +1000 Subject: [PATCH] Add `--latest` flag Allow using the latest version of the Unifi controller with `--latest`. --- fields/extract.go | 3 +- fields/main.go | 35 +++++++-- fields/version.go | 63 ++++++++++++++++ fields/version_test.go | 159 +++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 6 files changed, 255 insertions(+), 8 deletions(-) create mode 100644 fields/version.go create mode 100644 fields/version_test.go diff --git a/fields/extract.go b/fields/extract.go index 35dcc80..5f926d0 100644 --- a/fields/extract.go +++ b/fields/extract.go @@ -14,12 +14,13 @@ import ( "path/filepath" "strings" + "github.com/hashicorp/go-version" "github.com/iancoleman/strcase" "github.com/ulikunitz/xz" "github.com/xor-gate/ar" ) -func downloadJar(version, outputDir string) (string, error) { +func downloadJar(version *version.Version, outputDir string) (string, error) { url := fmt.Sprintf("https://dl.ui.com/unifi/%s/unifi_sysvinit_all.deb", version) debResp, err := http.Get(url) diff --git a/fields/main.go b/fields/main.go index 4c7ca72..9e11d76 100644 --- a/fields/main.go +++ b/fields/main.go @@ -17,6 +17,7 @@ import ( "strings" "text/template" + "github.com/hashicorp/go-version" "github.com/iancoleman/strcase" ) @@ -181,23 +182,43 @@ func usage() { } func main() { - flag.Usage = usage noEmbeddedTypesFlag := flag.Bool("no-embedded-types", true, "Whether to generate top-level type definitions for embedded type definitions") versionBaseDirFlag := flag.String("version-base-dir", ".", "The base directory for version JSON files") outputDirFlag := flag.String("output-dir", ".", "The output directory of the generated Go code") downloadOnly := flag.Bool("download-only", false, "Only download and build the fields JSON directory, do not generate") + useLatestVersion := flag.Bool("latest", false, "Use the latest available version") flag.Parse() embedTypes = !*noEmbeddedTypesFlag - version := flag.Arg(0) - if version == "" { - fmt.Print("error: no version specified\n\n") + specifiedVersion := flag.Arg(0) + if specifiedVersion != "" && *useLatestVersion { + fmt.Print("error: cannot specify version with latest\n\n") usage() os.Exit(1) + } else if specifiedVersion == "" && !*useLatestVersion { + fmt.Print("error: must specify version or latest\n\n") + usage() + os.Exit(1) + } + + var unifiVersion *version.Version + var err error + + if *useLatestVersion { + unifiVersion, err = latestUnifiVersion() + if err != nil { + panic(err) + } + } else { + unifiVersion, err = version.NewVersion(specifiedVersion) + if err != nil { + fmt.Println(err) + os.Exit(1) + } } wd, err := os.Getwd() @@ -205,7 +226,7 @@ func main() { panic(err) } - fieldsDir := filepath.Join(wd, *versionBaseDirFlag, fmt.Sprintf("v%s", version)) + fieldsDir := filepath.Join(wd, *versionBaseDirFlag, fmt.Sprintf("v%s", unifiVersion)) outDir := filepath.Join(wd, *outputDirFlag) fieldsInfo, err := os.Stat(fieldsDir) @@ -220,7 +241,7 @@ func main() { } // download fields, create - jarFile, err := downloadJar(version, fieldsDir) + jarFile, err := downloadJar(unifiVersion, fieldsDir) if err != nil { panic(err) } @@ -372,7 +393,7 @@ func main() { package unifi const UnifiVersion = %q -`, version) +`, unifiVersion) if err := ioutil.WriteFile(filepath.Join(outDir, "version.generated.go"), []byte(versionGo), 0644); err != nil { panic(err) } diff --git a/fields/version.go b/fields/version.go new file mode 100644 index 0000000..5d75987 --- /dev/null +++ b/fields/version.go @@ -0,0 +1,63 @@ +package main + +import ( + "encoding/json" + "net/http" + + "github.com/hashicorp/go-version" +) + +var uiDownloadUrl = "https://www.ui.com/download/?platform=unifi" + +func latestUnifiVersion() (*version.Version, error) { + client := &http.Client{} + + req, err := http.NewRequest("GET", uiDownloadUrl, nil) + if err != nil { + return nil, err + } + req.Header.Add("X-Requested-With", "XMLHttpRequest") + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var respData struct { + Downloads []struct { + Id int `json:"id"` + CategorySlug string `json:"category__slug"` + Filename string `json:"filename"` + Version string `json:"version"` + } `json:"downloads"` + } + var latestVersion *version.Version + + err = json.NewDecoder(resp.Body).Decode(&respData) + if err != nil { + return nil, err + } + + for _, download := range respData.Downloads { + if download.CategorySlug != "software" { + continue + } + + if download.Filename != "unifi_sysvinit_all.deb" { + continue + } + + downloadVersion, err := version.NewVersion(download.Version) + if err != nil { + // Skip this entry if the version isn't valid. + continue + } + + if latestVersion == nil || downloadVersion.GreaterThan(latestVersion) { + latestVersion = downloadVersion + } + } + + return latestVersion, nil +} diff --git a/fields/version_test.go b/fields/version_test.go new file mode 100644 index 0000000..f5bd0ed --- /dev/null +++ b/fields/version_test.go @@ -0,0 +1,159 @@ +package main + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/hashicorp/go-version" + "github.com/stretchr/testify/assert" +) + +func TestLatestUnifiVersion(t *testing.T) { + respData := map[string]interface{}{ + "downloads": []map[string]interface{}{ + { + "architecture": "", + "build": "", + "category__name": "User Guides", + "category__slug": "user-guides", + "changelog": "", + "date_published": "2018-03-05", + "description": "", + "featured": true, + "file_path": "/downloads/guides/UniFi/UniFi_Controller_V5_UG.pdf", + "filename": "UniFi_Controller_V5_UG.pdf", + "id": 898, + "mib": "", + "name": "UniFi® Controller v5 User Guide", + "products": "UAP|UAP-AC-EDU|UAP–AC–IW|UAP–AC–IW–PRO|UAP-AC-LITE|UAP-AC-LR|UAP-AC-M|UAP-AC-M-PRO|UAP-AC-PRO|UAP-HD|UAP-IW|UAP-LR|UAP-nanoHD|UAP-PRO|UAP-SHD|UAP‑XG|UAS-XG|UC-CK|US-16-150W|US‑16‑XG|US-24|US-24-250W|US-24-500W|US-48|US-48-500W|US-48-750W|US-8|US-8-150W|US-8-60W|USG|USG-PRO-4|USG-XG-8|US-XG-6POE|UWB‑XG|UWB‑XG‑BK", + "rank": 1, + "revision_history": "", + "sdk__id": nil, + "size": nil, + "slug": "unifi-controller-v5-user-guide", + "thumbnail": "https://prd-www-cdn.ubnt.com/media/images/download/user-guides/UniFi_Controller_V5_UG.jpg", + "thumbnail_retina": "https://prd-www-cdn.ubnt.com/media/images/download/user-guides/UniFi_Controller_V5_UG-2x.jpg", + "version": "", + }, + { + "architecture": "", + "build": "", + "category__name": "Software", + "category__slug": "software", + "changelog": "https://community.ui.com/releases/UniFi-Network-Controller-6-0-22/910ceffc-f0e9-4518-86c1-df5eeee34695", + "date_published": "2020-09-17", + "description": "UniFi Network Controller 6.0.22 for Debian/Ubuntu Linux and UniFi Cloud Key.", + "featured": false, + "file_path": "/downloads/unifi/6.0.22/unifi_sysvinit_all.deb", + "filename": "unifi_sysvinit_all.deb", + "id": 2607, + "mib": "", + "name": "UniFi Network Controller 6.0.22 for Debian/Ubuntu Linux and UniFi Cloud Key", + "products": "UAP|UAP-AC-EDU|UAP–AC–IW|UAP–AC–IW–PRO|UAP-AC-LITE|UAP-AC-LR|UAP-AC-M|UAP-AC-M-PRO|UAP-AC-PRO|UAP-BeaconHD|UAP-FlexHD|UAP-HD|UAP-IW|UAP-IW-HD|UAP-LR|UAP-nanoHD|UAP-Outdoor|UAP-Outdoor+|UAP-Outdoor5|UAP-PRO|UAP-SHD|UAP‑XG|UAS-XG|UBB|UC-CK|UCK-G2|UCK-G2-PLUS|US-16-150W|US‑16‑XG|US-24|US-24-250W|US-24-500W|US-48|US-48-500W|US-48-750W|US-8|US-8-150W|US-8-60W|USG|USG-PRO-4|USG-XG-8|US-L2-24-POE|US-L2-48-POE|USW-16-POE|USW-24-POE|USW-48-POE|USW-Flex|USW-Flex-Mini|USW-Industrial|USW-Lite-16-POE|USW-Pro-24-POE|USW-Pro-48-POE|US-XG-6POE|UWB‑XG|UWB‑XG‑BK", + "rank": 350, + "revision_history": "", + "sdk__id": nil, + "size": nil, + "slug": "unifi-network-controller-6022-debianubuntu-linux-and-unifi-cloud-key", + "thumbnail": nil, + "thumbnail_retina": nil, + "version": "6.0.22", + }, + { + "architecture": "", + "build": "", + "category__name": "Software", + "category__slug": "software", + "changelog": "https://community.ui.com/releases/0cffd3ed-7429-4529-9a20-9fead78ebf66", + "date_published": "2021-03-25", + "description": "UniFi Network Controller 6.1.71 for Debian/Ubuntu Linux and UniFi Cloud Key.", + "featured": false, + "file_path": "/downloads/unifi/6.1.71/unifi_sysvinit_all.deb", + "filename": "unifi_sysvinit_all.deb", + "id": 2777, + "mib": "", + "name": "UniFi Network Controller 6.1.71 for Debian/Ubuntu Linux and UniFi Cloud Key", + "products": "UAP|UAP-AC-EDU|UAP–AC–IW|UAP–AC–IW–PRO|UAP-AC-LITE|UAP-AC-LR|UAP-AC-M|UAP-AC-M-PRO|UAP-AC-PRO|UAP-BeaconHD|UAP-FlexHD|UAP-HD|UAP-IW|UAP-IW-HD|UAP-LR|UAP-nanoHD|UAP-Outdoor|UAP-Outdoor+|UAP-Outdoor5|UAP-PRO|UAP-SHD|UAP‑XG|UAS-XG|UBB|UC-CK|UCK-G2|UCK-G2-PLUS|US-16-150W|US‑16‑XG|US-24|US-24-250W|US-24-500W|US-48|US-48-500W|US-48-750W|US-8|US-8-150W|US-8-60W|USG|USG-PRO-4|USG-XG-8|US-L2-24-POE|US-L2-48-POE|USP-RPS|USW-16-POE|USW-24|USW-24-POE|USW-48|USW-48-POE|USW-Aggregation|USW-Flex|USW-Flex-Mini|USW-Industrial|USW-Lite-16-POE|USW-Lite-8-PoE|USW-Pro-24|USW-Pro-24-POE|USW-Pro-48|USW-Pro-48-POE|US-XG-6POE|UWB‑XG|UWB‑XG‑BK", + "rank": 423, + "revision_history": "", + "sdk__id": nil, + "size": nil, + "slug": "unifi-network-controller-6171-debianubuntu-linux-and-unifi-cloud-key", + "thumbnail": nil, + "thumbnail_retina": nil, + "version": "6.1.71", + }, + { + "architecture": "", + "build": "", + "category__name": "Software", + "category__slug": "software", + "changelog": "https://community.ui.com/releases/0dfcbc77-8a4f-4e20-bb93-07bbb0237e3a", + "date_published": "2021-06-21", + "description": "UniFi Network Application 6.2.26 for Debian/Ubuntu Linux and UniFi Cloud Key.", + "featured": true, + "file_path": "/downloads/unifi/6.2.26/unifi_sysvinit_all.deb", + "filename": "unifi_sysvinit_all.deb", + "id": 2840, + "mib": "", + "name": "UniFi Network Application 6.2.26 for Debian/Ubuntu Linux and UniFi Cloud Key", + "products": "UAP|UAP-AC-EDU|UAP–AC–IW|UAP–AC–IW–PRO|UAP-AC-LITE|UAP-AC-LR|UAP-AC-M|UAP-AC-M-PRO|UAP-AC-PRO|UAP-BeaconHD|UAP-FlexHD|UAP-HD|UAP-IW|UAP-IW-HD|UAP-LR|UAP-nanoHD|UAP-Outdoor|UAP-Outdoor+|UAP-Outdoor5|UAP-PRO|UAP-SHD|UAP‑XG|UAS-XG|UBB|UC-CK|UCK-G2|UCK-G2-PLUS|UDM|UDM-Pro|US-16-150W|US‑16‑XG|US-24|US-24-250W|US-24-500W|US-48|US-48-500W|US-48-750W|US-8|US-8-150W|US-8-60W|USG|USG-PRO-4|USG-XG-8|US-L2-24-POE|US-L2-48-POE|USP-RPS|USW-16-POE|USW-24|USW-24-POE|USW-48|USW-48-POE|USW-Aggregation|USW-Enterprise-24-PoE|USW-Flex|USW-Flex-Mini|USW-Industrial|USW-Lite-16-POE|USW-Lite-8-PoE|USW-Pro-24|USW-Pro-24-POE|USW-Pro-48|USW-Pro-48-POE|USW-Pro-Aggregation|US-XG-6POE|UWB‑XG|UWB‑XG‑BK", + "rank": 440, + "revision_history": "", + "sdk__id": nil, + "size": nil, + "slug": "unifi-network-application-6226-debianubuntu-linux-and-unifi-cloud-key", + "thumbnail": nil, + "thumbnail_retina": nil, + "version": "6.2.26", + }, + { + "architecture": "", + "build": "", + "category__name": "Firmware", + "category__slug": "firmware", + "changelog": "https://community.ui.com/releases/a98a71d1-ce1e-4823-a1d2-4a5fa3d642b9", + "date_published": "2021-07-14", + "description": "UniFi firmware 5.60.9 for U6-Lite", + "featured": true, + "file_path": "/downloads/unifi/firmware/UAL6/5.60.9.12980/BZ.mt7621_5.60.9+12980.210702.0701.bin", + "filename": "BZ.mt7621_5.60.9+12980.210702.0701.bin", + "id": 2847, + "mib": "", + "name": "UniFi firmware 5.60.9 for U6-Lite", + "products": "U6-Lite", + "rank": 444, + "revision_history": "", + "sdk__id": nil, + "size": nil, + "slug": "unifi-firmware-5609-u6-lite", + "thumbnail": nil, + "thumbnail_retina": nil, + "version": "5.60.9", + }, + }, + "products": []map[string]interface{}{}, + } + + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + assert.Equal(t, []string{"XMLHttpRequest"}, req.Header["X-Requested-With"]) + + resp, err := json.Marshal(respData) + assert.Nil(t, err) + + _, err = rw.Write(resp) + assert.Nil(t, err) + })) + defer server.Close() + + expected, err := version.NewVersion("6.2.26") + assert.Nil(t, err) + + uiDownloadUrl = server.URL + actual, err := latestUnifiVersion() + assert.Nil(t, err) + + assert.Equal(t, expected, actual) +} diff --git a/go.mod b/go.mod index a5a4469..68f03d7 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ module github.com/paultyng/go-unifi go 1.16 require ( + github.com/hashicorp/go-version v1.3.0 github.com/iancoleman/strcase v0.2.0 github.com/kr/pretty v0.1.0 // indirect github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 3fcee94..9e66dbd 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= +github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=