From 4fbabb1ac12bbb37d4749b59a3713340a932486a Mon Sep 17 00:00:00 2001 From: nitram509 Date: Sun, 12 May 2024 14:14:34 +0200 Subject: [PATCH 1/6] switch to Go1.22 --- .github/workflows/build.yml | 2 +- .github/workflows/go-test.yml | 2 +- .github/workflows/update-readme.yml | 2 +- CHANGELOG.md | 6 ++++++ go.mod | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ef1e29..18bc4b1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: - name: "setup go" uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.22' - name: Get the version id: get_version run: echo "VERSION=$(echo $GITHUB_REF | cut -d / -f 3)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index 86ea0e4..eeec199 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -16,7 +16,7 @@ jobs: - name: "setup go" uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.22' - name: "go mod download" run: "go mod download" - name: "go test ./..." diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml index ce79cc9..0fa1588 100644 --- a/.github/workflows/update-readme.yml +++ b/.github/workflows/update-readme.yml @@ -12,7 +12,7 @@ jobs: - name: "setup go" uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.22' - name: "build binary and print --help" run: | go build -o ntgrrc . diff --git a/CHANGELOG.md b/CHANGELOG.md index e07af5c..3280235 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # ntgrrc (Netgear Remote Control) CHANGELOG +## v0.10.1 + +* change using Go '1.22' + +---- + ## v0.10.0 * Add support for GS316EP(P) models - this is still experimental, as only showing the settings and status works; many thanks to @jsanchez73 and @crypto-smoke for testing and supporting this feature diff --git a/go.mod b/go.mod index e1f6933..b528e4a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module ntgrrc -go 1.20 +go 1.22 require ( github.com/PuerkitoBio/goquery v1.9.2 From ca80fe8f6b8817cfd81bc8b8a49d248a03717947 Mon Sep 17 00:00:00 2001 From: nitram509 Date: Sun, 12 May 2024 18:10:59 +0200 Subject: [PATCH 2/6] ignore just the binary --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5d0b19c..0ed7d34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ *.iml .idea .DS_Store -ntgrrc +/ntgrrc From 4588fa354e0a06d521456a00b6e9a3994bedb22e Mon Sep 17 00:00:00 2001 From: nitram509 Date: Mon, 13 May 2024 12:12:28 +0200 Subject: [PATCH 3/6] SHELF refactor project structure to separate CLI app from library --- cmd/ntgrrc/cli_options.go | 12 + cmd/ntgrrc/debug_report.go | 13 ++ help_all.go => cmd/ntgrrc/help_all.go | 0 cmd/ntgrrc/login.go | 45 ++++ main.go => cmd/ntgrrc/main.go | 23 +- cmd/ntgrrc/poe.go | 8 + cmd/ntgrrc/poe_cycle_power.go | 21 ++ cmd/ntgrrc/poe_set_power.go | 38 +++ cmd/ntgrrc/poe_settings.go | 19 ++ cmd/ntgrrc/poe_status.go | 20 ++ cmd/ntgrrc/port.go | 6 + cmd/ntgrrc/port_set.go | 34 +++ cmd/ntgrrc/port_settings.go | 22 ++ cmd/ntgrrc/util.go | 13 ++ util_test.go => cmd/ntgrrc/util_test.go | 0 version.go => cmd/ntgrrc/version.go | 2 +- formatter.go | 8 - go.mod | 2 +- go.sum | 3 + debug_report.go => pkg/ntgrrc/debug_report.go | 24 +- pkg/ntgrrc/example_test.go | 22 ++ http.go => pkg/ntgrrc/http.go | 18 +- login.go => pkg/ntgrrc/login.go | 55 ++--- login_test.go => pkg/ntgrrc/login_test.go | 4 +- .../ntgrrc/mapping/poe_value_mappings.go | 16 +- .../ntgrrc/mapping/poe_value_mappings_test.go | 4 +- .../ntgrrc/mapping/port_value_mappings.go | 14 +- .../ntgrrc/netgear_model.go | 34 +-- .../ntgrrc/netgear_model_test.go | 2 +- pkg/ntgrrc/ntgrrc_options.go | 14 ++ pkg/ntgrrc/poe_cycle.go | 52 +++++ .../ntgrrc/poe_set_power.go | 132 ++++++----- .../ntgrrc/poe_set_power_test.go | 70 +++--- pkg/ntgrrc/poe_settings.go | 85 +++++++ .../ntgrrc/poe_settings_test.go | 6 +- poe_status.go => pkg/ntgrrc/poe_status.go | 59 +---- .../ntgrrc/poe_status_test.go | 6 +- pkg/ntgrrc/port_set.go | 220 +++++++++++++++++ .../ntgrrc/port_set_test.go | 39 ++-- .../ntgrrc/port_settings.go | 54 +---- .../ntgrrc/port_settings_test.go | 2 +- pkg/ntgrrc/print.go | 99 ++++++++ formatter_json.go => pkg/ntgrrc/print_json.go | 2 +- .../ntgrrc/print_markdown.go | 2 +- token.go => pkg/ntgrrc/token.go | 10 +- token_test.go => pkg/ntgrrc/token_test.go | 8 +- util.go => pkg/ntgrrc/util.go | 11 +- pkg/ntgrrc/util_test.go | 18 ++ poe_cycle.go | 55 ----- poe_settings.go | 123 ---------- port_set.go | 221 ------------------ 51 files changed, 1003 insertions(+), 767 deletions(-) create mode 100644 cmd/ntgrrc/cli_options.go create mode 100644 cmd/ntgrrc/debug_report.go rename help_all.go => cmd/ntgrrc/help_all.go (100%) create mode 100644 cmd/ntgrrc/login.go rename main.go => cmd/ntgrrc/main.go (66%) create mode 100644 cmd/ntgrrc/poe.go create mode 100644 cmd/ntgrrc/poe_cycle_power.go create mode 100644 cmd/ntgrrc/poe_set_power.go create mode 100644 cmd/ntgrrc/poe_settings.go create mode 100644 cmd/ntgrrc/poe_status.go create mode 100644 cmd/ntgrrc/port.go create mode 100644 cmd/ntgrrc/port_set.go create mode 100644 cmd/ntgrrc/port_settings.go create mode 100644 cmd/ntgrrc/util.go rename util_test.go => cmd/ntgrrc/util_test.go (100%) rename version.go => cmd/ntgrrc/version.go (71%) delete mode 100644 formatter.go rename debug_report.go => pkg/ntgrrc/debug_report.go (78%) create mode 100644 pkg/ntgrrc/example_test.go rename http.go => pkg/ntgrrc/http.go (79%) rename login.go => pkg/ntgrrc/login.go (77%) rename login_test.go => pkg/ntgrrc/login_test.go (94%) rename poe_value_mappings.go => pkg/ntgrrc/mapping/poe_value_mappings.go (70%) rename poe_value_mappings_test.go => pkg/ntgrrc/mapping/poe_value_mappings_test.go (78%) rename port_value_mappings.go => pkg/ntgrrc/mapping/port_value_mappings.go (50%) rename netgear_model.go => pkg/ntgrrc/netgear_model.go (91%) rename netgear_model_test.go => pkg/ntgrrc/netgear_model_test.go (98%) create mode 100644 pkg/ntgrrc/ntgrrc_options.go create mode 100644 pkg/ntgrrc/poe_cycle.go rename poe_set_port.go => pkg/ntgrrc/poe_set_power.go (55%) rename poe_set_port_test.go => pkg/ntgrrc/poe_set_power_test.go (72%) create mode 100644 pkg/ntgrrc/poe_settings.go rename poe_settings_test.go => pkg/ntgrrc/poe_settings_test.go (96%) rename poe_status.go => pkg/ntgrrc/poe_status.go (65%) rename poe_status_test.go => pkg/ntgrrc/poe_status_test.go (97%) create mode 100644 pkg/ntgrrc/port_set.go rename port_set_test.go => pkg/ntgrrc/port_set_test.go (77%) rename port_settings.go => pkg/ntgrrc/port_settings.go (59%) rename port_settings_test.go => pkg/ntgrrc/port_settings_test.go (99%) create mode 100644 pkg/ntgrrc/print.go rename formatter_json.go => pkg/ntgrrc/print_json.go (97%) rename formatter_markdown.go => pkg/ntgrrc/print_markdown.go (98%) rename token.go => pkg/ntgrrc/token.go (89%) rename token_test.go => pkg/ntgrrc/token_test.go (88%) rename util.go => pkg/ntgrrc/util.go (82%) create mode 100644 pkg/ntgrrc/util_test.go delete mode 100644 poe_cycle.go delete mode 100644 poe_settings.go delete mode 100644 port_set.go diff --git a/cmd/ntgrrc/cli_options.go b/cmd/ntgrrc/cli_options.go new file mode 100644 index 0000000..873a082 --- /dev/null +++ b/cmd/ntgrrc/cli_options.go @@ -0,0 +1,12 @@ +package main + +import "github.com/nitram509/ntgrrc/pkg/ntgrrc" + +type CliOptions struct { + Verbose bool + Quiet bool + OutputFormat ntgrrc.PrintFormat + TokenDir string + model ntgrrc.NetgearModel + token string +} diff --git a/cmd/ntgrrc/debug_report.go b/cmd/ntgrrc/debug_report.go new file mode 100644 index 0000000..e63b205 --- /dev/null +++ b/cmd/ntgrrc/debug_report.go @@ -0,0 +1,13 @@ +package main + +import ( + "github.com/nitram509/ntgrrc/pkg/ntgrrc" +) + +type DebugReportCommand struct { + Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` +} + +func (drc *DebugReportCommand) Run(args *CliOptions) error { + return ntgrrc.PrintDebugReport(drc.Address) +} diff --git a/help_all.go b/cmd/ntgrrc/help_all.go similarity index 100% rename from help_all.go rename to cmd/ntgrrc/help_all.go diff --git a/cmd/ntgrrc/login.go b/cmd/ntgrrc/login.go new file mode 100644 index 0000000..ff5aa65 --- /dev/null +++ b/cmd/ntgrrc/login.go @@ -0,0 +1,45 @@ +package main + +import ( + "errors" + "fmt" + "github.com/nitram509/ntgrrc/pkg/ntgrrc" + "golang.org/x/term" + "syscall" +) + +type LoginCommand struct { + Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` + Password string `optional:"" help:"the admin console's password; if omitted, it will be prompted for" short:"p"` +} + +func (login *LoginCommand) Run(args CliOptions) error { + if len(login.Password) < 1 { + pwd, err := promptForPassword(login.Address) + if err != nil { + return err + } + login.Password = pwd + } + + if len(login.Password) < 1 { + return errors.New("no password given") + } + + session := ntgrrc.NtgrrcSession{ + PrintVerbose: args.Verbose, + TokenDir: args.TokenDir, + } + + err := session.DoLogin(login.Address, login.Password) + + return err +} + +func promptForPassword(serverName string) (string, error) { + fmt.Printf("Please enter password for '%s' (input hidden) :> ", serverName) + // the int conversion is required for the windows build to succeed + password, err := term.ReadPassword(int(syscall.Stdin)) + println() + return string(password), err +} diff --git a/main.go b/cmd/ntgrrc/main.go similarity index 66% rename from main.go rename to cmd/ntgrrc/main.go index fd47220..e183ad2 100644 --- a/main.go +++ b/cmd/ntgrrc/main.go @@ -3,24 +3,17 @@ package main import ( "fmt" "github.com/alecthomas/kong" + "github.com/nitram509/ntgrrc/pkg/ntgrrc" "os" ) -type GlobalOptions struct { - Verbose bool - Quiet bool - OutputFormat OutputFormat - TokenDir string - model NetgearModel - token string -} - var cli struct { - HelpAll HelpAllFlag `help:"advanced/full help"` - Verbose bool `help:"verbose log messages" short:"v"` - Quiet bool `help:"no log messages" short:"q"` - OutputFormat OutputFormat `help:"what output format to use [md, json]" enum:"md,json" default:"md" short:"f"` - TokenDir string `help:"directory to store login tokens" default:"" short:"d"` + HelpAll HelpAllFlag `help:"advanced/full help"` + Verbose bool `help:"verbose log messages" short:"v"` + // FIXME: this seems to not being used ... so remove + Quiet bool `help:"no log messages" short:"q"` + OutputFormat ntgrrc.PrintFormat `help:"what output format to use [md, json]" enum:"md,json" default:"md" short:"f"` + TokenDir string `help:"directory to store login tokens" default:"" short:"d"` Version VersionCommand `cmd:"" name:"version" help:"show version"` Login LoginCommand `cmd:"" name:"login" help:"create a session for further commands (requires admin console password)"` @@ -43,7 +36,7 @@ func main() { }), ) - err := options.Run(&GlobalOptions{ + err := options.Run(&CliOptions{ Verbose: cli.Verbose, Quiet: cli.Quiet, OutputFormat: cli.OutputFormat, diff --git a/cmd/ntgrrc/poe.go b/cmd/ntgrrc/poe.go new file mode 100644 index 0000000..b8e1d93 --- /dev/null +++ b/cmd/ntgrrc/poe.go @@ -0,0 +1,8 @@ +package main + +type PoeCommand struct { + PoeStatusCommand PoeStatusCommand `cmd:"" name:"status" help:"show current PoE status for all ports" default:"1"` + PoeShowSettingsCommand PoeShowSettingsCommand `cmd:"" name:"settings" help:"show current PoE settings for all ports"` + PoeSetPowerCommand PoeSetPowerCommand `cmd:"" name:"set" help:"set new PoE settings per each PORT number"` + PoeCyclePowerCommand PoeCyclePowerCommand `cmd:"" name:"cycle" help:"power cycle one or more PoE ports"` +} diff --git a/cmd/ntgrrc/poe_cycle_power.go b/cmd/ntgrrc/poe_cycle_power.go new file mode 100644 index 0000000..d1ea67b --- /dev/null +++ b/cmd/ntgrrc/poe_cycle_power.go @@ -0,0 +1,21 @@ +package main + +import "github.com/nitram509/ntgrrc/pkg/ntgrrc" + +type PoeCyclePowerCommand struct { + Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` + Ports []int `required:"" help:"port number (starting with 1), use multiple times for cycling multiple ports at once" short:"p" name:"port"` +} + +func (pcp *PoeCyclePowerCommand) Run(args *CliOptions) error { + session := ntgrrc.NtgrrcSession{ + PrintVerbose: args.Verbose, + TokenDir: args.TokenDir, + } + changedPorts, err := session.PoeCyclePower(pcp.Ports) + if err != nil { + return err + } + ntgrrc.PrettyPrintPoePortSettings(args.OutputFormat, changedPorts) + return nil +} diff --git a/cmd/ntgrrc/poe_set_power.go b/cmd/ntgrrc/poe_set_power.go new file mode 100644 index 0000000..bc0bd5d --- /dev/null +++ b/cmd/ntgrrc/poe_set_power.go @@ -0,0 +1,38 @@ +package main + +import "github.com/nitram509/ntgrrc/pkg/ntgrrc" + +type PoeSetPowerCommand struct { + Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` + Ports []int `required:"" help:"port number (starting with 1), use multiple times for setting multiple ports at once" short:"p" name:"port"` + PortPwr string `optional:"" help:"power state for port [enable, disable]" short:"s" name:"power"` + PwrMode string `optional:"" help:"power mode [802.3af, legacy, pre-802.3at, 802.3at]" short:"m" name:"mode"` + PortPrio string `optional:"" help:"priority [low, high, critical]" short:"r" name:"priority"` + LimitType string `optional:"" help:"power limit type [none, class, user]" short:"t" name:"limit-type"` + PwrLimit string `optional:"" help:"power limit (W)" short:"l" name:"pwr-limit"` + DetecType string `optional:"" help:"detection type [IEEE 802, legacy, 4pt 802.3af + Legacy]" short:"e" name:"detect-type"` + LongerDetect string `optional:"" help:"longer detection time [enable, disable]" name:"longer-detection-time"` +} + +func (psp *PoeSetPowerCommand) Run(args *CliOptions) error { + session := ntgrrc.NtgrrcSession{ + PrintVerbose: args.Verbose, + TokenDir: args.TokenDir, + } + poeSetPower := ntgrrc.SetPoePowerRequest{ + Ports: psp.Ports, + PortPwr: psp.PortPwr, + PwrMode: psp.PwrMode, + PortPrio: psp.PortPrio, + LimitType: psp.LimitType, + PwrLimit: psp.PwrLimit, + DetecType: psp.DetecType, + LongerDetect: psp.LongerDetect, + } + changedPorts, err := session.SetPoePower(poeSetPower) + if err != nil { + return err + } + ntgrrc.PrettyPrintPoePortSettings(args.OutputFormat, changedPorts) + return err +} diff --git a/cmd/ntgrrc/poe_settings.go b/cmd/ntgrrc/poe_settings.go new file mode 100644 index 0000000..e771801 --- /dev/null +++ b/cmd/ntgrrc/poe_settings.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/nitram509/ntgrrc/pkg/ntgrrc" +) + +type PoeShowSettingsCommand struct { + Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` +} + +func (poe *PoeShowSettingsCommand) Run(args *CliOptions) error { + session := ntgrrc.NtgrrcSession{} + settings, err := session.GetPoeSettings() + if err != nil { + return err + } + ntgrrc.PrettyPrintPoePortSettings(args.OutputFormat, settings) + return nil +} diff --git a/cmd/ntgrrc/poe_status.go b/cmd/ntgrrc/poe_status.go new file mode 100644 index 0000000..6d0ff16 --- /dev/null +++ b/cmd/ntgrrc/poe_status.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/nitram509/ntgrrc/pkg/ntgrrc" +) + +type PoeStatusCommand struct { + Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` +} + +func (poe *PoeStatusCommand) Run(args *CliOptions) error { + session := ntgrrc.NtgrrcSession{ + PrintVerbose: args.Verbose, + TokenDir: args.TokenDir, + } + + status, err := session.GetPoePortStatus() + ntgrrc.PrettyPrintStatus(args.OutputFormat, status) + return err +} diff --git a/cmd/ntgrrc/port.go b/cmd/ntgrrc/port.go new file mode 100644 index 0000000..7cf2461 --- /dev/null +++ b/cmd/ntgrrc/port.go @@ -0,0 +1,6 @@ +package main + +type PortCommand struct { + PortSettingsCommand PortSettingsCommand `cmd:"" name:"settings" help:"show switch port settings" default:"1"` + PortSetCommand PortSetCommand `cmd:"" name:"set" help:"set new settings/properties for each port"` +} diff --git a/cmd/ntgrrc/port_set.go b/cmd/ntgrrc/port_set.go new file mode 100644 index 0000000..d3b7f43 --- /dev/null +++ b/cmd/ntgrrc/port_set.go @@ -0,0 +1,34 @@ +package main + +import "github.com/nitram509/ntgrrc/pkg/ntgrrc" + +type PortSetCommand struct { + Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` + Ports []int `required:"" help:"port number (starting with 1), use multiple times for setting multiple ports at once" short:"p" name:"port"` + Name *string `optional:"" help:"sets the name of a port, 1-16 character limit" short:"n"` + Speed string `optional:"" help:"set the speed and duplex of the port ['100M full', '100M half', '10M full', '10M half', 'Auto', 'Disable']" short:"s"` + IngressRateLimit string `optional:"" help:"set an incoming rate limit for the port ['1 Mbit/s', '128 Mbit/s', '16 Mbit/s', '2 Mbit/s', '256 Mbit/s', '32 Mbit/s', '4 Mbit/s', '512 Kbit/s', '512 Mbit/s', '64 Mbit/s', '8 Mbit/s', 'No Limit']" short:"i"` + EgressRateLimit string `optional:"" help:"set an outgoing rate limit for the port ['1 Mbit/s', '128 Mbit/s', '16 Mbit/s', '2 Mbit/s', '256 Mbit/s', '32 Mbit/s', '4 Mbit/s', '512 Kbit/s', '512 Mbit/s', '64 Mbit/s', '8 Mbit/s', 'No Limit']" short:"o"` + FlowControl string `optional:"" help:"enable/disable flow control on port ['Off', 'On']"` +} + +func (ps *PortSetCommand) Run(args *CliOptions) error { + session := ntgrrc.NtgrrcSession{ + PrintVerbose: args.Verbose, + TokenDir: args.TokenDir, + } + req := ntgrrc.SetPortSettingsRequest{ + Ports: ps.Ports, + Name: ps.Name, + Speed: ps.Speed, + IngressRateLimit: ps.IngressRateLimit, + EgressRateLimit: ps.EgressRateLimit, + FlowControl: ps.FlowControl, + } + changed, err := session.SetPortSettings(req) + if err != nil { + return err + } + ntgrrc.PrettyPrintPortSettings(args.OutputFormat, changed) + return nil +} diff --git a/cmd/ntgrrc/port_settings.go b/cmd/ntgrrc/port_settings.go new file mode 100644 index 0000000..a3a65e7 --- /dev/null +++ b/cmd/ntgrrc/port_settings.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/nitram509/ntgrrc/pkg/ntgrrc" +) + +type PortSettingsCommand struct { + Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` +} + +func (ps *PortSettingsCommand) Run(args *CliOptions) error { + session := ntgrrc.NtgrrcSession{ + PrintVerbose: args.Verbose, + TokenDir: args.TokenDir, + } + settings, err := session.GetPortSettings() + if err != nil { + return err + } + ntgrrc.PrettyPrintPortSettings(args.OutputFormat, settings) + return nil +} diff --git a/cmd/ntgrrc/util.go b/cmd/ntgrrc/util.go new file mode 100644 index 0000000..d1d0aea --- /dev/null +++ b/cmd/ntgrrc/util.go @@ -0,0 +1,13 @@ +package main + +import ( + "strings" +) + +func suffixToLength(s string, length int) string { + if len(s) < length { + diff := length - len(s) + return s + strings.Repeat(" ", diff) + } + return s +} diff --git a/util_test.go b/cmd/ntgrrc/util_test.go similarity index 100% rename from util_test.go rename to cmd/ntgrrc/util_test.go diff --git a/version.go b/cmd/ntgrrc/version.go similarity index 71% rename from version.go rename to cmd/ntgrrc/version.go index 91774f0..c7b9740 100644 --- a/version.go +++ b/cmd/ntgrrc/version.go @@ -6,7 +6,7 @@ var VERSION = "dev" type VersionCommand struct { } -func (version *VersionCommand) Run(args *GlobalOptions) error { +func (version *VersionCommand) Run(args *CliOptions) error { println(VERSION) return nil } diff --git a/formatter.go b/formatter.go deleted file mode 100644 index 7799c8e..0000000 --- a/formatter.go +++ /dev/null @@ -1,8 +0,0 @@ -package main - -type OutputFormat string - -const ( - MarkdownFormat OutputFormat = "md" - JsonFormat OutputFormat = "json" -) diff --git a/go.mod b/go.mod index b528e4a..bbed910 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module ntgrrc +module github.com/nitram509/ntgrrc go 1.22 diff --git a/go.sum b/go.sum index a8d027a..f3eeb29 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,17 @@ github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= +github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/corbym/gocrest v1.1.1 h1:lry77EvxdkHVL9XaPf0uHTcRPZi9jOXvUbdxhV7djYc= github.com/corbym/gocrest v1.1.1/go.mod h1:vhNebfdBGx5l0Nh0OM/CvIVqGAnR9AAbI5qA9OxRUOU= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/debug_report.go b/pkg/ntgrrc/debug_report.go similarity index 78% rename from debug_report.go rename to pkg/ntgrrc/debug_report.go index 3c58eea..f62a0a0 100644 --- a/debug_report.go +++ b/pkg/ntgrrc/debug_report.go @@ -1,25 +1,25 @@ -package main +package ntgrrc import ( "fmt" ) -type DebugReportCommand struct { - Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` -} - -func (drc *DebugReportCommand) Run(args *GlobalOptions) error { - args.Verbose = true - model, _, err := readTokenAndModel2GlobalOptions(args, drc.Address) +// PrintDebugReport connects to multiple endpoints of the switch and prints their response to the console (STDOUT) +// this is only useful for debugging use cases +func PrintDebugReport(address string) error { + args := NewSession() + args.PrintVerbose = true + // FIXME: sessions should not be loaded from disk + model, _, err := readTokenAndModel2GlobalOptions(args, address) if err != nil { println("Warning, prior error: " + err.Error()) - printDebugNotLoggedIn(args, drc.Address, err) + printDebugNotLoggedIn(args, address, err) } - printDebugLoggedIn(args, model, drc.Address) + printDebugLoggedIn(args, model, address) return nil } -func printDebugNotLoggedIn(args *GlobalOptions, host string, err error) { +func printDebugNotLoggedIn(args *NtgrrcSession, host string, err error) { println("---[DEBUG: not logged in]---") println(fmt.Sprintf("Not logged in error: %s", err)) println("Please try to login and run `debug-report` command again, in order to detect the model and get even more debug information") @@ -42,7 +42,7 @@ func printDebugNotLoggedIn(args *GlobalOptions, host string, err error) { println("---[/DEBUG]---") } -func printDebugLoggedIn(args *GlobalOptions, model NetgearModel, host string) { +func printDebugLoggedIn(args *NtgrrcSession, model NetgearModel, host string) { var reqUrls []string if !isModel30x(model) { reqUrls = append(reqUrls, diff --git a/pkg/ntgrrc/example_test.go b/pkg/ntgrrc/example_test.go new file mode 100644 index 0000000..72cfdf1 --- /dev/null +++ b/pkg/ntgrrc/example_test.go @@ -0,0 +1,22 @@ +package ntgrrc + +func ExampleNtgrrcSession_DoLogin() { + host := "127.0.0.1" // the IP address or host name + passw := "secret" + session := NewSession() + err := session.DoLogin(host, passw) + if err != nil { + panic(err) + } +} + +func ExampleNtgrrcSession_DetectNetgearModel() { + host := "127.0.0.1" // the IP address or host name + session := NewSession() + model, err := session.DetectNetgearModel(host) + if err != nil { + panic(err) + } + println(model) + // Output: GS308EPP +} diff --git a/http.go b/pkg/ntgrrc/http.go similarity index 79% rename from http.go rename to pkg/ntgrrc/http.go index f1a3072..2e12914 100644 --- a/http.go +++ b/pkg/ntgrrc/http.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "fmt" @@ -7,21 +7,21 @@ import ( "strings" ) -func requestPage(args *GlobalOptions, host string, url string) (string, error) { +func requestPage(args *NtgrrcSession, host string, url string) (string, error) { return doHttpRequestAndReadResponse(args, http.MethodGet, host, url, "") } -func postPage(args *GlobalOptions, host string, url string, requestBody string) (string, error) { +func postPage(args *NtgrrcSession, host string, url string, requestBody string) (string, error) { return doHttpRequestAndReadResponse(args, http.MethodPost, host, url, requestBody) } -func doHttpRequestAndReadResponse(args *GlobalOptions, httpMethod string, host string, requestUrl string, requestBody string) (string, error) { +func doHttpRequestAndReadResponse(args *NtgrrcSession, httpMethod string, host string, requestUrl string, requestBody string) (string, error) { model, token, err := readTokenAndModel2GlobalOptions(args, host) if err != nil { return "", err } - if args.Verbose { + if args.PrintVerbose { println("Fetching data from: " + requestUrl) } @@ -49,15 +49,15 @@ func doHttpRequestAndReadResponse(args *GlobalOptions, httpMethod string, host s return "", err } defer resp.Body.Close() - if args.Verbose { + if args.PrintVerbose { println(resp.Status) } bytes, err := io.ReadAll(resp.Body) return string(bytes), err } -func doUnauthenticatedHttpRequestAndReadResponse(args *GlobalOptions, httpMethod string, requestUrl string, requestBody string) (string, error) { - if args.Verbose { +func doUnauthenticatedHttpRequestAndReadResponse(args *NtgrrcSession, httpMethod string, requestUrl string, requestBody string) (string, error) { + if args.PrintVerbose { println("Fetching data from: " + requestUrl) } @@ -72,7 +72,7 @@ func doUnauthenticatedHttpRequestAndReadResponse(args *GlobalOptions, httpMethod return "", err } defer resp.Body.Close() - if args.Verbose { + if args.PrintVerbose { println(resp.Status) for name, values := range resp.Header { for _, value := range values { diff --git a/login.go b/pkg/ntgrrc/login.go similarity index 77% rename from login.go rename to pkg/ntgrrc/login.go index 958671e..8b68ea3 100644 --- a/login.go +++ b/pkg/ntgrrc/login.go @@ -1,68 +1,43 @@ -package main +package ntgrrc import ( "crypto/md5" "errors" "fmt" "github.com/PuerkitoBio/goquery" - "golang.org/x/term" "io" "math" "net/http" "strings" - "syscall" ) const FailedAttempt = "no SID cookie found in response header" -type LoginCommand struct { - Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` - Password string `optional:"" help:"the admin console's password; if omitted, it will be prompted for" short:"p"` -} - -func (login *LoginCommand) Run(args *GlobalOptions) error { - if len(login.Password) < 1 { - pwd, err := promptForPassword(login.Address) - if err != nil { - return err - } - login.Password = pwd - } - - if len(login.Password) < 1 { - return errors.New("no password given") - } - - model, err := detectNetgearModel(args, login.Address) +// DoLogin initializes the session for further use (by fetching and storing a token) +func (session *NtgrrcSession) DoLogin(address string, password string) error { + model, err := session.DetectNetgearModel(address) if err != nil { return err } - args.model = model + session.model = model - seedValue, err := getSeedValueFromSwitch(args, login.Address) + seedValue, err := getSeedValueFromSwitch(session, address) if err != nil { return err } - encryptedPwd := encryptPassword(login.Password, seedValue) + encryptedPwd := encryptPassword(password, seedValue) - err = doLogin(args, login.Address, encryptedPwd) + err = doLoginRequest(session, password, encryptedPwd) if err != nil { return err } + session.address = address return nil } -func promptForPassword(serverName string) (string, error) { - fmt.Printf("Please enter password for '%s' (input hidden) :> ", serverName) - // the int conversion is required for the windows build to succeed - password, err := term.ReadPassword(int(syscall.Stdin)) - println() - return string(password), err -} - -func doLogin(args *GlobalOptions, host string, encryptedPwd string) error { +func doLoginRequest(args *NtgrrcSession, host string, encryptedPwd string) error { var url string if isModel30x(args.model) { url = fmt.Sprintf("http://%s/login.cgi", host) @@ -71,7 +46,7 @@ func doLogin(args *GlobalOptions, host string, encryptedPwd string) error { } else { return errors.New("Unknown model not supported, please contact the developers ") } - if args.Verbose { + if args.PrintVerbose { println("login attempt: " + url) } @@ -87,7 +62,7 @@ func doLogin(args *GlobalOptions, host string, encryptedPwd string) error { return err } defer resp.Body.Close() - if args.Verbose { + if args.PrintVerbose { println(resp.Status) } body, err := io.ReadAll(resp.Body) @@ -156,7 +131,7 @@ func findGambitTokenInResponseHtml(reader io.Reader) (gambitToken string) { return gambitToken } -func getSeedValueFromSwitch(args *GlobalOptions, host string) (string, error) { +func getSeedValueFromSwitch(args *NtgrrcSession, host string) (string, error) { var url string if isModel30x(args.model) { url = fmt.Sprintf("http://%s/login.cgi", host) @@ -165,14 +140,14 @@ func getSeedValueFromSwitch(args *GlobalOptions, host string) (string, error) { } else { return "", errors.New("Unknown model not supported, please contact the developers ") } - if args.Verbose { + if args.PrintVerbose { println("fetch seed value from: " + url) } resp, err := http.Get(url) if err != nil { return "", err } - if args.Verbose { + if args.PrintVerbose { println(resp.Status) } defer resp.Body.Close() diff --git a/login_test.go b/pkg/ntgrrc/login_test.go similarity index 94% rename from login_test.go rename to pkg/ntgrrc/login_test.go index 69f3c0d..7597dd7 100644 --- a/login_test.go +++ b/pkg/ntgrrc/login_test.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "github.com/corbym/gocrest/is" @@ -56,7 +56,7 @@ func TestFindGambitTokenInResponseHtml(t *testing.T) { } func loadTestFile(model string, fileName string) string { - fullFileName := filepath.Join("test-data", model, fileName) + fullFileName := filepath.Join("..", "..", "test-data", model, fileName) bytes, err := os.ReadFile(fullFileName) if err != nil { panic(err) diff --git a/poe_value_mappings.go b/pkg/ntgrrc/mapping/poe_value_mappings.go similarity index 70% rename from poe_value_mappings.go rename to pkg/ntgrrc/mapping/poe_value_mappings.go index e42ba42..acfb6cd 100644 --- a/poe_value_mappings.go +++ b/pkg/ntgrrc/mapping/poe_value_mappings.go @@ -1,4 +1,4 @@ -package main +package mapping import ( "sort" @@ -6,7 +6,7 @@ import ( ) // bidiMapLookup bidirectional map lookup, will return either key or value depending on the input -func bidiMapLookup(value string, mapName map[string]string) string { +func BidiMapLookup(value string, mapName map[string]string) string { if val, ok := mapName[value]; ok { return val } else { @@ -21,7 +21,7 @@ func bidiMapLookup(value string, mapName map[string]string) string { } // comma separated string list, alphabetically sorted -func valuesAsString(strMap map[string]string) string { +func ValuesAsString(strMap map[string]string) string { var vals []string for _, val := range strMap { vals = append(vals, val) @@ -30,32 +30,32 @@ func valuesAsString(strMap map[string]string) string { return strings.Join(vals, ", ") } -var pwrModeMap = map[string]string{ +var PwrModeMap = map[string]string{ "0": "802.3af", "1": "legacy", "2": "pre-802.3at", "3": "802.3at", } -var portPrioMap = map[string]string{ +var PortPrioMap = map[string]string{ "0": "low", "2": "high", "3": "critical", } -var limitTypeMap = map[string]string{ +var LimitTypeMap = map[string]string{ "0": "none", "1": "class", "2": "user", } -var detecTypeMap = map[string]string{ +var DetecTypeMap = map[string]string{ "1": "Legacy", "2": "IEEE 802", "3": "4pt 802.3af + Legacy", } -var longerDetectMap = map[string]string{ +var LongerDetectMap = map[string]string{ "0": "Get Value Fault", "2": "disable", "3": "enable", diff --git a/poe_value_mappings_test.go b/pkg/ntgrrc/mapping/poe_value_mappings_test.go similarity index 78% rename from poe_value_mappings_test.go rename to pkg/ntgrrc/mapping/poe_value_mappings_test.go index 2d45417..4debea6 100644 --- a/poe_value_mappings_test.go +++ b/pkg/ntgrrc/mapping/poe_value_mappings_test.go @@ -1,4 +1,4 @@ -package main +package mapping import ( "github.com/corbym/gocrest/is" @@ -7,7 +7,7 @@ import ( ) func TestValues(t *testing.T) { - str := valuesAsString(portPrioMap) + str := ValuesAsString(PortPrioMap) then.AssertThat(t, str, is.EqualTo("critical, high, low")) } diff --git a/port_value_mappings.go b/pkg/ntgrrc/mapping/port_value_mappings.go similarity index 50% rename from port_value_mappings.go rename to pkg/ntgrrc/mapping/port_value_mappings.go index 35ceee2..3b65fe5 100644 --- a/port_value_mappings.go +++ b/pkg/ntgrrc/mapping/port_value_mappings.go @@ -1,8 +1,8 @@ -package main +// Package mapping provides helper functions for handling map lookup and (verbose) printing values +package mapping -// helper functions for handling map lookup and dumping values in poe_value_mappings.go - -var portSpeedMap = map[string]string{ +// PortSpeedMap mappings for speed settings +var PortSpeedMap = map[string]string{ "1": "Auto", "2": "Disable", "3": "10M half", @@ -11,8 +11,8 @@ var portSpeedMap = map[string]string{ "6": "100M full", } -// Rate limit mapping is similar for both for Ingress and Egress options -var portRateLimitMap = map[string]string{ +// PortRateLimitMap Rate limit mapping is similar for both for Ingress and Egress options +var PortRateLimitMap = map[string]string{ "1": "No Limit", "2": "512 Kbit/s", "3": "1 Mbit/s", @@ -27,7 +27,7 @@ var portRateLimitMap = map[string]string{ "12": "512 Mbit/s", } -var portFlowControlMap = map[string]string{ +var PortFlowControlMap = map[string]string{ "1": "On", "2": "Off", } diff --git a/netgear_model.go b/pkg/ntgrrc/netgear_model.go similarity index 91% rename from netgear_model.go rename to pkg/ntgrrc/netgear_model.go index 3d5bf42..2a1c437 100644 --- a/netgear_model.go +++ b/pkg/ntgrrc/netgear_model.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "errors" @@ -20,28 +20,16 @@ const ( GS316EPP NetgearModel = "GS316EPP" ) -func isModel30x(nm NetgearModel) bool { - return nm == GS305EP || nm == GS305EPP || nm == GS308EP || nm == GS308EPP || nm == GS30xEPx -} - -func isModel316(nm NetgearModel) bool { - return nm == GS316EP || nm == GS316EPP -} - -func isSupportedModel(modelName string) bool { - return isModel30x(NetgearModel(modelName)) || isModel316(NetgearModel(modelName)) -} - -func detectNetgearModel(args *GlobalOptions, host string) (NetgearModel, error) { +func (session *NtgrrcSession) DetectNetgearModel(host string) (NetgearModel, error) { url := fmt.Sprintf("http://%s/", host) - if args.Verbose { + if session.PrintVerbose { println("detecting Netgear switch model: " + url) } resp, err := http.Get(url) if err != nil { return "", err } - if args.Verbose { + if session.PrintVerbose { println(fmt.Sprintf("HTTP response code %d", resp.StatusCode)) } if resp.StatusCode != 200 { @@ -56,7 +44,7 @@ func detectNetgearModel(args *GlobalOptions, host string) (NetgearModel, error) if model == "" { return "", errors.New("Can't auto-detect Netgear model from response. You may try using --model parameter ") } - if args.Verbose { + if session.PrintVerbose { println(fmt.Sprintf("Detected model %s", model)) } return model, nil @@ -74,3 +62,15 @@ func detectNetgearModelFromResponse(body string) NetgearModel { } return "" } + +func isModel30x(nm NetgearModel) bool { + return nm == GS305EP || nm == GS305EPP || nm == GS308EP || nm == GS308EPP || nm == GS30xEPx +} + +func isModel316(nm NetgearModel) bool { + return nm == GS316EP || nm == GS316EPP +} + +func isSupportedModel(modelName string) bool { + return isModel30x(NetgearModel(modelName)) || isModel316(NetgearModel(modelName)) +} diff --git a/netgear_model_test.go b/pkg/ntgrrc/netgear_model_test.go similarity index 98% rename from netgear_model_test.go rename to pkg/ntgrrc/netgear_model_test.go index a800f57..cf030be 100644 --- a/netgear_model_test.go +++ b/pkg/ntgrrc/netgear_model_test.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "github.com/corbym/gocrest/is" diff --git a/pkg/ntgrrc/ntgrrc_options.go b/pkg/ntgrrc/ntgrrc_options.go new file mode 100644 index 0000000..f264415 --- /dev/null +++ b/pkg/ntgrrc/ntgrrc_options.go @@ -0,0 +1,14 @@ +package ntgrrc + +// NtgrrcSession configuration options for all commands +type NtgrrcSession struct { + PrintVerbose bool + TokenDir string + model NetgearModel + token string + address string +} + +func NewSession() *NtgrrcSession { + return &NtgrrcSession{} +} diff --git a/pkg/ntgrrc/poe_cycle.go b/pkg/ntgrrc/poe_cycle.go new file mode 100644 index 0000000..445f897 --- /dev/null +++ b/pkg/ntgrrc/poe_cycle.go @@ -0,0 +1,52 @@ +package ntgrrc + +import ( + "errors" + "fmt" + "net/url" +) + +// PoeCyclePower does a power cycle per each given port, whereas +// the given slice contains port numbers (starting with 1) for which the power cycle will happen. +// Returns an ordered list of type PoePortSetting with the new settings of each provided port number +func (session *NtgrrcSession) PoeCyclePower(ports []int) ([]PoePortSetting, error) { + err := ensureModelIs30x(session, session.address) + if err != nil { + return nil, err + } + + poeExt := &poeExtValues{} + + settings, err := requestPoeConfiguration(session, session.address, poeExt) + if err != nil { + return nil, err + } + + poeSettings := url.Values{ + "hash": {poeExt.Hash}, + "ACTION": {"Reset"}, + } + + for _, switchPort := range ports { + if switchPort > len(settings) || switchPort < 1 { + return nil, errors.New(fmt.Sprintf("given port id %d, doesn't fit in range 1..%d", switchPort, len(settings))) + } + poeSettings.Add(fmt.Sprintf("port%d", switchPort-1), "checked") + } + + result, err := requestPoeSettingsUpdate(session, session.address, poeSettings.Encode()) + if result != "SUCCESS" { + return nil, errors.New(result) + } + if err != nil { + return nil, err + } + + settings, err = requestPoeConfiguration(session, session.address, poeExt) + if err != nil { + return nil, err + } + + changedPorts := collectChangedPoePortConfiguration(ports, settings) + return changedPorts, nil +} diff --git a/poe_set_port.go b/pkg/ntgrrc/poe_set_power.go similarity index 55% rename from poe_set_port.go rename to pkg/ntgrrc/poe_set_power.go index dced5a3..c0f232c 100644 --- a/poe_set_port.go +++ b/pkg/ntgrrc/poe_set_power.go @@ -1,60 +1,62 @@ -package main +package ntgrrc import ( "errors" "fmt" "github.com/PuerkitoBio/goquery" + "github.com/nitram509/ntgrrc/pkg/ntgrrc/mapping" "io" "net/url" "strconv" "strings" ) -type Setting string +type SetPoePowerRequest struct { + Ports []int // port number (starting with 1), use multiple times for setting multiple ports at once + PortPwr string // power state for port [enable, disable] + PwrMode string // power mode [802.3af, legacy, pre-802.3at, 802.3at] + PortPrio string // priority [low, high, critical] + LimitType string // power limit type [none, class, user] + PwrLimit string // power limit (W) + DetecType string // detection type [IEEE 802, legacy, 4pt 802.3af + Legacy] + LongerDetect string // longer detection time [enable, disable] +} + +type setting string const ( - PortPrio Setting = "PortPrio" - PwrMode Setting = "PwrMode" - LimitType Setting = "LimitType" - PwrLimit Setting = "PwrLimit" - DetecType Setting = "DetecType" - LongerDetect Setting = "LongerDetect" + portPrioSetting setting = "PortPrio" + pwrModeSetting setting = "PwrMode" + limitTypeSetting setting = "LimitType" + pwrLimitSetting setting = "PwrLimit" + detecTypeSetting setting = "DetecType" + longerDetectSetting setting = "LongerDetect" ) -type PoeSetPowerCommand struct { - Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` - Ports []int `required:"" help:"port number (starting with 1), use multiple times for setting multiple ports at once" short:"p" name:"port"` - PortPwr string `optional:"" help:"power state for port [enable, disable]" short:"s" name:"power"` - PwrMode string `optional:"" help:"power mode [802.3af, legacy, pre-802.3at, 802.3at]" short:"m" name:"mode"` - PortPrio string `optional:"" help:"priority [low, high, critical]" short:"r" name:"priority"` - LimitType string `optional:"" help:"power limit type [none, class, user]" short:"t" name:"limit-type"` - PwrLimit string `optional:"" help:"power limit (W)" short:"l" name:"pwr-limit"` - DetecType string `optional:"" help:"detection type [IEEE 802, legacy, 4pt 802.3af + Legacy]" short:"e" name:"detect-type"` - LongerDetect string `optional:"" help:"longer detection time [enable, disable]" name:"longer-detection-time"` -} - -type PoeExt struct { +type poeExtValues struct { Hash string PortMaxPower string } -func (poe *PoeSetPowerCommand) Run(args *GlobalOptions) error { - err := ensureModelIs30x(args, poe.Address) +// SetPoePower sets new POE power settings and return an ordered list of type PoePortSetting +// with the resulting changes from the switch +func (session *NtgrrcSession) SetPoePower(poe SetPoePowerRequest) ([]PoePortSetting, error) { + err := ensureModelIs30x(session, session.address) if err != nil { - return err + return nil, err } - poeExt := &PoeExt{} + poeExt := &poeExtValues{} var adminMode string - settings, err := requestPoeConfiguration(args, poe.Address, poeExt) + settings, err := requestPoeConfiguration(session, session.address, poeExt) if err != nil { - return err + return nil, err } for _, switchPort := range poe.Ports { if switchPort > len(settings) || switchPort < 1 { - return errors.New(fmt.Sprintf("given port id %d, doesn't fit in range 1..%d", switchPort, len(settings))) + return nil, errors.New(fmt.Sprintf("given port id %d, doesn't fit in range 1..%d", switchPort, len(settings))) } portSetting := settings[switchPort-1] @@ -71,32 +73,32 @@ func (poe *PoeSetPowerCommand) Run(args *GlobalOptions) error { } } - portPrio, err := comparePoeSettings(PortPrio, portSetting.PortPrio, poe.PortPrio, poeExt) + portPrio, err := comparePoeSettings(portPrioSetting, portSetting.PortPrio, poe.PortPrio, poeExt) if err != nil { - return err + return nil, err } - pwrMode, err := comparePoeSettings(PwrMode, portSetting.PwrMode, poe.PwrMode, poeExt) + pwrMode, err := comparePoeSettings(pwrModeSetting, portSetting.PwrMode, poe.PwrMode, poeExt) if err != nil { - return err + return nil, err } - pwrLimitType, err := comparePoeSettings(LimitType, portSetting.LimitType, poe.LimitType, poeExt) + pwrLimitType, err := comparePoeSettings(limitTypeSetting, portSetting.LimitType, poe.LimitType, poeExt) if err != nil { - return err + return nil, err } - pwrLimit, err := comparePoeSettings(PwrLimit, portSetting.PwrLimit, poe.PwrLimit, poeExt) + pwrLimit, err := comparePoeSettings(pwrLimitSetting, portSetting.PwrLimit, poe.PwrLimit, poeExt) if err != nil { - return err + return nil, err } - detecType, err := comparePoeSettings(DetecType, portSetting.DetecType, poe.DetecType, poeExt) + detecType, err := comparePoeSettings(detecTypeSetting, portSetting.DetecType, poe.DetecType, poeExt) if err != nil { - return err + return nil, err } - longerDetect, err := comparePoeSettings(LongerDetect, portSetting.LongerDetect, poe.LongerDetect, poeExt) + longerDetect, err := comparePoeSettings(longerDetectSetting, portSetting.LongerDetect, poe.LongerDetect, poeExt) poeSettings := url.Values{ "hash": {poeExt.Hash}, @@ -111,23 +113,19 @@ func (poe *PoeSetPowerCommand) Run(args *GlobalOptions) error { "DISCONNECT_TYP": {longerDetect}, } - result, err := requestPoeSettingsUpdate(args, poe.Address, poeSettings.Encode()) + result, err := requestPoeSettingsUpdate(session, session.address, poeSettings.Encode()) if err != nil { - return err + return nil, err } if result != "SUCCESS" { - return errors.New(result) + return nil, errors.New(result) } } - settings, err = requestPoeConfiguration(args, poe.Address, poeExt) - + settings, err = requestPoeConfiguration(session, session.address, poeExt) changedPorts := collectChangedPoePortConfiguration(poe.Ports, settings) - - prettyPrintSettings(args.OutputFormat, changedPorts) - - return err + return changedPorts, err } func collectChangedPoePortConfiguration(poePorts []int, settings []PoePortSetting) (changedPorts []PoePortSetting) { @@ -142,7 +140,7 @@ func collectChangedPoePortConfiguration(poePorts []int, settings []PoePortSettin return changedPorts } -func requestPoeConfiguration(args *GlobalOptions, host string, poeExt *PoeExt) ([]PoePortSetting, error) { +func requestPoeConfiguration(args *NtgrrcSession, host string, poeExt *poeExtValues) ([]PoePortSetting, error) { var settings []PoePortSetting @@ -173,7 +171,7 @@ func requestPoeConfiguration(args *GlobalOptions, host string, poeExt *PoeExt) ( return settings, nil } -func requestPoeSettingsUpdate(args *GlobalOptions, host string, data string) (string, error) { +func requestPoeSettingsUpdate(args *NtgrrcSession, host string, data string) (string, error) { url := fmt.Sprintf("http://%s/PoEPortConfig.cgi", host) return postPage(args, host, url, data) } @@ -208,31 +206,31 @@ func findMaxPwrLimitInHtml(reader io.Reader) (string, error) { return limit, err } -func comparePoeSettings(name Setting, defaultValue string, newValue string, poeExt *PoeExt) (string, error) { +func comparePoeSettings(name setting, defaultValue string, newValue string, poeExt *poeExtValues) (string, error) { if len(newValue) == 0 { return defaultValue, nil } switch name { - case PortPrio: - portPrio := bidiMapLookup(newValue, portPrioMap) + case portPrioSetting: + portPrio := mapping.BidiMapLookup(newValue, mapping.PortPrioMap) if portPrio == "unknown" { - return portPrio, errors.New("port priority could not be set. Accepted values are: " + valuesAsString(portPrioMap)) + return portPrio, errors.New("port priority could not be set. Accepted values are: " + mapping.ValuesAsString(mapping.PortPrioMap)) } return portPrio, nil - case PwrMode: - pwrMode := bidiMapLookup(newValue, pwrModeMap) + case pwrModeSetting: + pwrMode := mapping.BidiMapLookup(newValue, mapping.PwrModeMap) if pwrMode == "unknown" { - return pwrMode, errors.New("power mode could not be set. Accepted values are: " + valuesAsString(pwrModeMap)) + return pwrMode, errors.New("power mode could not be set. Accepted values are: " + mapping.ValuesAsString(mapping.PwrModeMap)) } return pwrMode, nil - case LimitType: - limitType := bidiMapLookup(newValue, limitTypeMap) + case limitTypeSetting: + limitType := mapping.BidiMapLookup(newValue, mapping.LimitTypeMap) if limitType == "unknown" { - return limitType, errors.New("limit type could not be set. Accepted values are: " + valuesAsString(limitTypeMap)) + return limitType, errors.New("limit type could not be set. Accepted values are: " + mapping.ValuesAsString(mapping.LimitTypeMap)) } return limitType, nil - case PwrLimit: + case pwrLimitSetting: if defaultValue != newValue { value, err := strconv.Atoi(strings.Replace(newValue, ".", "", -1)) if err != nil { @@ -255,16 +253,16 @@ func comparePoeSettings(name Setting, defaultValue string, newValue string, poeE return newValue, nil } return defaultValue, nil - case DetecType: - detecType := bidiMapLookup(newValue, detecTypeMap) + case detecTypeSetting: + detecType := mapping.BidiMapLookup(newValue, mapping.DetecTypeMap) if detecType == "unknown" { - return detecType, errors.New("detection type could not be set. Accepted values are: " + valuesAsString(detecTypeMap)) + return detecType, errors.New("detection type could not be set. Accepted values are: " + mapping.ValuesAsString(mapping.DetecTypeMap)) } return detecType, nil - case LongerDetect: - longerDetect := bidiMapLookup(newValue, longerDetectMap) + case longerDetectSetting: + longerDetect := mapping.BidiMapLookup(newValue, mapping.LongerDetectMap) if longerDetect == "unknown" { - return longerDetect, errors.New("longer detection type value could not be set. Accepted values are: " + valuesAsString(longerDetectMap)) + return longerDetect, errors.New("longer detection type value could not be set. Accepted values are: " + mapping.ValuesAsString(mapping.LongerDetectMap)) } return longerDetect, nil default: diff --git a/poe_set_port_test.go b/pkg/ntgrrc/poe_set_power_test.go similarity index 72% rename from poe_set_port_test.go rename to pkg/ntgrrc/poe_set_power_test.go index 704b406..5ad084e 100644 --- a/poe_set_port_test.go +++ b/pkg/ntgrrc/poe_set_power_test.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "github.com/corbym/gocrest/is" @@ -7,7 +7,7 @@ import ( "testing" ) -var poeExt = &PoeExt{ +var poeExt = &poeExtValues{ Hash: "4f11f5d64ef3fd75a92a9f2ad1de3060", PortMaxPower: "30.0", } @@ -68,7 +68,7 @@ func TestFindMaxPoePowerLimit(t *testing.T) { func TestComparePoeSettingsUnknown(t *testing.T) { - for _, setting := range []Setting{PortPrio, PwrMode, LimitType, DetecType, LongerDetect} { + for _, setting := range []setting{portPrioSetting, pwrModeSetting, limitTypeSetting, detecTypeSetting, longerDetectSetting} { setting, _ := comparePoeSettings(setting, "defaultValue", "newValue", poeExt) then.AssertThat(t, setting, is.EqualTo("unknown").Reason("when providing a value that does not exist, return unknown to the caller")) } @@ -76,134 +76,134 @@ func TestComparePoeSettingsUnknown(t *testing.T) { func TestComparePoeSettingsPwrLimit(t *testing.T) { - pwrLimit, err := comparePoeSettings(PwrLimit, "3.0", "30.0", poeExt) + pwrLimit, err := comparePoeSettings(pwrLimitSetting, "3.0", "30.0", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, pwrLimit, is.EqualTo("30.0").Reason("allow values up to the maximum power in PortMaxPower")) - pwrLimitDefault, err := comparePoeSettings(PwrLimit, "15.0", "15.0", poeExt) + pwrLimitDefault, err := comparePoeSettings(pwrLimitSetting, "15.0", "15.0", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, pwrLimitDefault, is.EqualTo("15.0").Reason("pass the default back if user did not change value")) - pwrLimitOutOfRange, _ := comparePoeSettings(PwrLimit, "30.0", "99999999.0", poeExt) + pwrLimitOutOfRange, _ := comparePoeSettings(pwrLimitSetting, "30.0", "99999999.0", poeExt) then.AssertThat(t, pwrLimitOutOfRange, is.EqualTo("30.0").Reason("use the default value if power limit is out of range")) - pwrLimitMidRange, _ := comparePoeSettings(PwrLimit, "30.0", "15", poeExt) + pwrLimitMidRange, _ := comparePoeSettings(pwrLimitSetting, "30.0", "15", poeExt) then.AssertThat(t, pwrLimitMidRange, is.EqualTo("15").Reason("integer values should work")) } func TestComparePoePortPrio(t *testing.T) { - setting, err := comparePoeSettings(PortPrio, "critical", "low", poeExt) + setting, err := comparePoeSettings(portPrioSetting, "critical", "low", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("0").Reason("allow user to change port priority to low")) - setting, err = comparePoeSettings(PortPrio, "low", "critical", poeExt) + setting, err = comparePoeSettings(portPrioSetting, "low", "critical", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("3").Reason("allow user to change port priority to critical")) - setting, err = comparePoeSettings(PortPrio, "low", "high", poeExt) + setting, err = comparePoeSettings(portPrioSetting, "low", "high", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("2").Reason("allow user to change port priority to high")) - setting, err = comparePoeSettings(PortPrio, "low", "low", poeExt) + setting, err = comparePoeSettings(portPrioSetting, "low", "low", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("0").Reason("maintain the same port priority")) - setting, err = comparePoeSettings(PortPrio, "0", "", poeExt) + setting, err = comparePoeSettings(portPrioSetting, "0", "", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("0").Reason("maintain the prior value when new nothing is specified")) } func TestComparePoePwrMode(t *testing.T) { - setting, err := comparePoeSettings(PwrMode, "802.3af", "legacy", poeExt) + setting, err := comparePoeSettings(pwrModeSetting, "802.3af", "legacy", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("1").Reason("allow user to change the power mode to legacy")) - setting, err = comparePoeSettings(PwrMode, "legacy", "pre-802.3at", poeExt) + setting, err = comparePoeSettings(pwrModeSetting, "legacy", "pre-802.3at", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("2").Reason("allow user to change the power mode to pre-802.3at")) - setting, err = comparePoeSettings(PwrMode, "pre-802.3at", "802.3at", poeExt) + setting, err = comparePoeSettings(pwrModeSetting, "pre-802.3at", "802.3at", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("3").Reason("allow user to change the power mode to 802.3at")) - setting, err = comparePoeSettings(PwrMode, "802.3af", "802.3af", poeExt) + setting, err = comparePoeSettings(pwrModeSetting, "802.3af", "802.3af", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("0").Reason("maintain the same power mode")) - setting, err = comparePoeSettings(PwrMode, "0", "", poeExt) + setting, err = comparePoeSettings(pwrModeSetting, "0", "", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("0").Reason("maintain the prior value when nothing new is specified")) } func TestComparePoeLimitType(t *testing.T) { - setting, err := comparePoeSettings(LimitType, "user", "none", poeExt) + setting, err := comparePoeSettings(limitTypeSetting, "user", "none", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("0").Reason("allow user to change the limit type to none")) - setting, err = comparePoeSettings(LimitType, "none", "class", poeExt) + setting, err = comparePoeSettings(limitTypeSetting, "none", "class", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("1").Reason("allow user to change the limit type to class")) - setting, err = comparePoeSettings(LimitType, "class", "user", poeExt) + setting, err = comparePoeSettings(limitTypeSetting, "class", "user", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("2").Reason("allow user to change the limit type to user")) - setting, err = comparePoeSettings(LimitType, "user", "user", poeExt) + setting, err = comparePoeSettings(limitTypeSetting, "user", "user", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("2").Reason("maintain the same limit type")) - setting, err = comparePoeSettings(LimitType, "2", "", poeExt) + setting, err = comparePoeSettings(limitTypeSetting, "2", "", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("2").Reason("maintain the prior value when nothing new is specified")) } func TestComparePoeDetecType(t *testing.T) { - setting, err := comparePoeSettings(DetecType, "IEEE 802", "Legacy", poeExt) + setting, err := comparePoeSettings(detecTypeSetting, "IEEE 802", "Legacy", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("1").Reason("allow user to change the detect type to Legacy")) - setting, err = comparePoeSettings(DetecType, "Legacy", "4pt 802.3af + Legacy", poeExt) + setting, err = comparePoeSettings(detecTypeSetting, "Legacy", "4pt 802.3af + Legacy", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("3").Reason("allow user to change the detect type to 4pt 802.3af + Legacy")) - setting, err = comparePoeSettings(DetecType, "4pt 802.3af + Legacy", "IEEE 802", poeExt) + setting, err = comparePoeSettings(detecTypeSetting, "4pt 802.3af + Legacy", "IEEE 802", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("2").Reason("allow user to change the detect type to IEEE 802")) - setting, err = comparePoeSettings(DetecType, "IEEE 802", "IEEE 802", poeExt) + setting, err = comparePoeSettings(detecTypeSetting, "IEEE 802", "IEEE 802", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("2").Reason("maintain the same detect type")) - setting, err = comparePoeSettings(DetecType, "1", "", poeExt) + setting, err = comparePoeSettings(detecTypeSetting, "1", "", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("1").Reason("maintain the prior value when nothing new is specified")) } func TestComparePoeLongerDetect(t *testing.T) { - setting, err := comparePoeSettings(LongerDetect, "Get Value Fault", "disable", poeExt) + setting, err := comparePoeSettings(longerDetectSetting, "Get Value Fault", "disable", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("2").Reason("allow user to change the longer detection time to Disable from Get Value Fault")) - setting, err = comparePoeSettings(LongerDetect, "Get Value Fault", "enable", poeExt) + setting, err = comparePoeSettings(longerDetectSetting, "Get Value Fault", "enable", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("3").Reason("allow user to change the longer detection time to Enable from Get Value Fault")) - setting, err = comparePoeSettings(LongerDetect, "enable", "disable", poeExt) + setting, err = comparePoeSettings(longerDetectSetting, "enable", "disable", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("2").Reason("allow user to change the longer detection time to Disable")) - setting, err = comparePoeSettings(LongerDetect, "disable", "enable", poeExt) + setting, err = comparePoeSettings(longerDetectSetting, "disable", "enable", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("3").Reason("allow user to change the longer detection time to Enable")) - setting, err = comparePoeSettings(LongerDetect, "enable", "", poeExt) + setting, err = comparePoeSettings(longerDetectSetting, "enable", "", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("enable").Reason("maintain the same longer detect type when nothing new is specified")) - setting, err = comparePoeSettings(LongerDetect, "2", "", poeExt) + setting, err = comparePoeSettings(longerDetectSetting, "2", "", poeExt) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, setting, is.EqualTo("2").Reason("maintain the same longer detect type when nothing new is specified")) } @@ -211,11 +211,11 @@ func TestComparePoeLongerDetect(t *testing.T) { func TestCollectChangedPoePortConfiguration(t *testing.T) { var ports = []int{1, 2} var settings = []PoePortSetting{ - PoePortSetting{ + { PortIndex: 1, PortName: "port 1", }, - PoePortSetting{ + { PortIndex: 2, PortName: "port 2", }, diff --git a/pkg/ntgrrc/poe_settings.go b/pkg/ntgrrc/poe_settings.go new file mode 100644 index 0000000..66f8d56 --- /dev/null +++ b/pkg/ntgrrc/poe_settings.go @@ -0,0 +1,85 @@ +package ntgrrc + +import ( + "errors" + "fmt" + "github.com/PuerkitoBio/goquery" + "io" + "strconv" + "strings" +) + +type PoePortSetting struct { + PortIndex int8 // port number (starting with 1) + PortName string + PortPwr bool + PwrMode string + PortPrio string + LimitType string + PwrLimit string + DetecType string + LongerDetect string +} + +func (session *NtgrrcSession) GetPoeSettings() ([]PoePortSetting, error) { + err := ensureModelIs30x(session, session.address) + if err != nil { + return nil, err + } + + settingsPage, err := requestPoePortConfigPage(session, session.address) + if err != nil { + return nil, err + } + if checkIsLoginRequired(settingsPage) { + return nil, errors.New("no content. please, (re-)login first") + } + var settings []PoePortSetting + settings, err = findPoeSettingsInHtml(strings.NewReader(settingsPage)) + if err != nil { + return nil, err + } + + return settings, nil +} + +func requestPoePortConfigPage(args *NtgrrcSession, host string) (string, error) { + url := fmt.Sprintf("http://%s/PoEPortConfig.cgi", host) + return requestPage(args, host, url) +} + +func findPoeSettingsInHtml(reader io.Reader) ([]PoePortSetting, error) { + doc, err := goquery.NewDocumentFromReader(reader) + if err != nil { + return nil, err + } + + var configs []PoePortSetting + doc.Find("li.poePortSettingListItem").Each(func(i int, s *goquery.Selection) { + config := PoePortSetting{} + + id, _ := s.Find("input[type=hidden].port").Attr("value") + var id64, _ = strconv.ParseInt(id, 10, 8) + config.PortIndex = int8(id64) + + config.PortName, _ = s.Find("input[type=hidden].portName").Attr("value") + + portWr, exists := s.Find("input#hidPortPwr").Attr("value") + config.PortPwr = exists && portWr == "1" + + config.PwrMode, _ = s.Find("input#hidPwrMode").Attr("value") + + config.PortPrio, _ = s.Find("input#hidPortPrio").Attr("value") + + config.LimitType, _ = s.Find("input#hidLimitType").Attr("value") + + config.PwrLimit, _ = s.Find("input.pwrLimit").Attr("value") + + config.DetecType, _ = s.Find("input#hidDetecType").Attr("value") + + config.LongerDetect, _ = s.Find("input.longerDetect").Attr("value") + + configs = append(configs, config) + }) + return configs, nil +} diff --git a/poe_settings_test.go b/pkg/ntgrrc/poe_settings_test.go similarity index 96% rename from poe_settings_test.go rename to pkg/ntgrrc/poe_settings_test.go index 0aed470..541a404 100644 --- a/poe_settings_test.go +++ b/pkg/ntgrrc/poe_settings_test.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "strings" @@ -103,7 +103,7 @@ func TestPrettyPrintSettings(t *testing.T) { then.AssertThat(t, err, is.Nil()) then.AssertThat(t, settings, has.Length[PoePortSetting](test.expectedVal)) - prettyPrintSettings(MarkdownFormat, settings) + PrettyPrintPoePortSettings(MarkdownFormat, settings) }) } } @@ -132,7 +132,7 @@ func TestPrettyPrintJsonSettings(t *testing.T) { then.AssertThat(t, err, is.Nil()) then.AssertThat(t, settings, has.Length[PoePortSetting](test.expectedVal)) - prettyPrintSettings(JsonFormat, settings) + PrettyPrintPoePortSettings(JsonFormat, settings) }) } } diff --git a/poe_status.go b/pkg/ntgrrc/poe_status.go similarity index 65% rename from poe_status.go rename to pkg/ntgrrc/poe_status.go index 208b6a3..6dff3bd 100644 --- a/poe_status.go +++ b/pkg/ntgrrc/poe_status.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "errors" @@ -10,7 +10,7 @@ import ( ) type PoePortStatus struct { - PortIndex int8 + PortIndex int8 // port number (starting with 1) PortName string PoePowerClass string PoePortStatus string @@ -21,61 +21,24 @@ type PoePortStatus struct { TemperatureInCelsius int32 } -type PoeCommand struct { - PoeStatusCommand PoeStatusCommand `cmd:"" name:"status" help:"show current PoE status for all ports" default:"1"` - PoeShowSettingsCommand PoeShowSettingsCommand `cmd:"" name:"settings" help:"show current PoE settings for all ports"` - PoeSetPowerCommand PoeSetPowerCommand `cmd:"" name:"set" help:"set new PoE settings per each PORT number"` - PoeCyclePowerCommand PoeCyclePowerCommand `cmd:"" name:"cycle" help:"power cycle one or more PoE ports"` -} - -type PoeStatusCommand struct { - Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` -} - -func (poe *PoeStatusCommand) Run(args *GlobalOptions) error { - statusPage, err := requestPoePortStatusPage(args, poe.Address) +// GetPoePortStatus returns information about each port of the switch in an ordered list of type PoePortStatus +func (session *NtgrrcSession) GetPoePortStatus() ([]PoePortStatus, error) { + statusPage, err := requestPoePortStatusPage(session, session.address) if err != nil { - return err + return nil, err } if checkIsLoginRequired(statusPage) { - return errors.New("no content. please, (re-)login first") + return nil, errors.New("no content. please, (re-)login first") } var statuses []PoePortStatus - statuses, err = findPortStatusInHtml(args.model, strings.NewReader(statusPage)) + statuses, err = findPortStatusInHtml(session.model, strings.NewReader(statusPage)) if err != nil { - return err - } - prettyPrintStatus(args.OutputFormat, statuses) - return nil -} - -func prettyPrintStatus(format OutputFormat, statuses []PoePortStatus) { - var header = []string{"Port ID", "Port Name", "Status", "PortPwr class", "Voltage (V)", "Current (mA)", "PortPwr (W)", "Temp. (°C)", "Error status"} - var content [][]string - for _, status := range statuses { - var row []string - row = append(row, fmt.Sprintf("%d", status.PortIndex)) - row = append(row, status.PortName) - row = append(row, status.PoePortStatus) - row = append(row, status.PoePowerClass) - row = append(row, fmt.Sprintf("%d", status.VoltageInVolt)) - row = append(row, fmt.Sprintf("%d", status.CurrentInMilliAmps)) - row = append(row, fmt.Sprintf("%.2f", status.PowerInWatt)) - row = append(row, fmt.Sprintf("%d", status.TemperatureInCelsius)) - row = append(row, status.ErrorStatus) - content = append(content, row) - } - switch format { - case MarkdownFormat: - printMarkdownTable(header, content) - case JsonFormat: - printJsonDataTable("poe_status", header, content) - default: - panic("not implemented format: " + format) + return nil, err } + return statuses, nil } -func requestPoePortStatusPage(args *GlobalOptions, host string) (string, error) { +func requestPoePortStatusPage(args *NtgrrcSession, host string) (string, error) { model, _, err := readTokenAndModel2GlobalOptions(args, host) if err != nil { return "", err diff --git a/poe_status_test.go b/pkg/ntgrrc/poe_status_test.go similarity index 97% rename from poe_status_test.go rename to pkg/ntgrrc/poe_status_test.go index ac3fa14..ed1e4e5 100644 --- a/poe_status_test.go +++ b/pkg/ntgrrc/poe_status_test.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "strings" @@ -119,7 +119,7 @@ func TestPrettyPrintMarkdownStatus(t *testing.T) { then.AssertThat(t, err, is.Nil()) then.AssertThat(t, statuses, has.Length[PoePortStatus](test.expectedVal)) - prettyPrintStatus(MarkdownFormat, statuses) + PrettyPrintStatus(MarkdownFormat, statuses) }) } } @@ -153,7 +153,7 @@ func TestPrettyPrintJsonStatus(t *testing.T) { then.AssertThat(t, err, is.Nil()) then.AssertThat(t, statuses, has.Length[PoePortStatus](test.expectedVal)) - prettyPrintStatus(JsonFormat, statuses) + PrettyPrintStatus(JsonFormat, statuses) }) } } diff --git a/pkg/ntgrrc/port_set.go b/pkg/ntgrrc/port_set.go new file mode 100644 index 0000000..c31b428 --- /dev/null +++ b/pkg/ntgrrc/port_set.go @@ -0,0 +1,220 @@ +package ntgrrc + +import ( + "errors" + "fmt" + "github.com/nitram509/ntgrrc/pkg/ntgrrc/mapping" + "net/url" + "strings" +) + +const ( + indexSetting setting = "Index" + nameSetting setting = "Name" + speedSetting setting = "Speed" + ingressRateLimitSetting setting = "IngressRateLimit" + egressRateLimitSetting setting = "EgressRateLimit" + flowControlSetting setting = "FlowControl" +) + +// Port used to show port settings +type Port struct { + Index int8 // port number (starting with 1) + Name string + Speed string + IngressRateLimit string + EgressRateLimit string + FlowControl string +} + +// SetPortSettingsRequest used to change port settings +type SetPortSettingsRequest struct { + Ports []int // port number (starting with 1), use multiple times for setting multiple ports at once + Name *string // sets the name of a port, 1-16 character limit; use nil, to keep the current name + Speed string // set the speed and duplex of the port ['100M full', '100M half', '10M full', '10M half', 'Auto', 'Disable'] + IngressRateLimit string // set an incoming rate limit for the port ['1 Mbit/s', '128 Mbit/s', '16 Mbit/s', '2 Mbit/s', '256 Mbit/s', '32 Mbit/s', '4 Mbit/s', '512 Kbit/s', '512 Mbit/s', '64 Mbit/s', '8 Mbit/s', 'No Limit'] + EgressRateLimit string // set an outgoing rate limit for the port ['1 Mbit/s', '128 Mbit/s', '16 Mbit/s', '2 Mbit/s', '256 Mbit/s', '32 Mbit/s', '4 Mbit/s', '512 Kbit/s', '512 Mbit/s', '64 Mbit/s', '8 Mbit/s', 'No Limit'] + FlowControl string // enable/disable flow control on port ['Off', 'On'] +} + +func (session *NtgrrcSession) SetPortSettings(portSet SetPortSettingsRequest) ([]Port, error) { + currentSettings, hash, err := requestPortSettings(session, session.address) + if err != nil { + return nil, err + } + + err = ensureModelIs30x(session, session.address) + if err != nil { + return nil, err + } + + for _, switchPort := range portSet.Ports { + + if switchPort > len(currentSettings) || switchPort < 1 { + return nil, errors.New(fmt.Sprintf("given port id %d, doesn't fit in range 1..%d", switchPort, len(currentSettings))) + } + + portSetting := currentSettings[switchPort-1] + + // If the port name was not set by the user, set it to the existing name (otherwise an empty port name is always considered to be the + // "new" value which blanks the port name on the setting next update) + if portSet.Name == nil { + portSet.Name = &portSetting.Name + } + + name, err := comparePortSettings(nameSetting, portSetting.Name, *portSet.Name) + if err != nil { + return nil, err + } + + speed, err := comparePortSettings(speedSetting, portSetting.Speed, portSet.Speed) + if err != nil { + return nil, err + } + + inRateLimit, err := comparePortSettings(ingressRateLimitSetting, portSetting.IngressRateLimit, portSet.IngressRateLimit) + if err != nil { + return nil, err + } + + outRateLimit, err := comparePortSettings(egressRateLimitSetting, portSetting.EgressRateLimit, portSet.EgressRateLimit) + if err != nil { + return nil, err + } + + flowControl, err := comparePortSettings(flowControlSetting, portSetting.FlowControl, portSet.FlowControl) + if err != nil { + return nil, err + } + + portUpdateValues := url.Values{ + "hash": {hash}, + fmt.Sprintf("%s%d", "port", portSetting.Index): {"checked"}, + "SPEED": {speed}, + "FLOW_CONTROL": {flowControl}, + "DESCRIPTION": {name}, + "IngressRate": {inRateLimit}, + "EgressRate": {outRateLimit}, + "priority": {"0"}, + } + + result, err := requestPortSettingsUpdate(session, session.address, portUpdateValues.Encode()) + if err != nil { + return nil, err + } + + if result != "SUCCESS" { + return nil, errors.New(result) + } + } + + currentSettings, _, err = requestPortSettings(session, session.address) + if err != nil { + return nil, err + } + + changedPorts := collectChangedPortConfiguration(portSet.Ports, currentSettings) + + return changedPorts, err +} + +func collectChangedPortConfiguration(ports []int, settings []Port) (changedPorts []Port) { + for _, configuredPort := range ports { + for _, portSetting := range settings { + if int(portSetting.Index) == configuredPort { + changedPorts = append(changedPorts, portSetting) + } + } + } + + return changedPorts +} + +func comparePortSettings(name setting, defaultValue string, newValue string) (string, error) { + if len(newValue) == 0 && name != nameSetting { + return defaultValue, nil + } + + switch name { + case nameSetting: + if defaultValue != newValue { + if len(newValue) <= 16 { + return newValue, nil + } else { + return defaultValue, errors.New("port name could not be set. Port name must be 16 characters or less") + } + } + return defaultValue, nil + case speedSetting: + speed := mapping.BidiMapLookup(newValue, mapping.PortSpeedMap) + if speed == "unknown" { + return speed, errors.New("port speed could not be set. Accepted values are: " + mapping.ValuesAsString(mapping.PortSpeedMap)) + } + return speed, nil + case ingressRateLimitSetting: + inRateLimit := mapping.BidiMapLookup(newValue, mapping.PortRateLimitMap) + if inRateLimit == "unknown" { + return inRateLimit, errors.New("ingress rate limit could not be set. Accepted values are: " + mapping.ValuesAsString(mapping.PortRateLimitMap)) + } + return inRateLimit, nil + case egressRateLimitSetting: + outRateLimit := mapping.BidiMapLookup(newValue, mapping.PortRateLimitMap) + if outRateLimit == "unknown" { + return outRateLimit, errors.New("egress rate limit could not be set. Accepted values are: " + mapping.ValuesAsString(mapping.PortRateLimitMap)) + } + return outRateLimit, nil + case flowControlSetting: + flowControl := mapping.BidiMapLookup(newValue, mapping.PortFlowControlMap) + if flowControl == "unknown" { + return flowControl, errors.New("flow control could not be set. Accepted values are: " + mapping.ValuesAsString(mapping.PortFlowControlMap)) + } + return flowControl, nil + default: + return defaultValue, errors.New("could not find port setting") + } + +} + +func requestPortSettings(args *NtgrrcSession, host string) (portSettings []Port, hash string, err error) { + model, _, err := readTokenAndModel2GlobalOptions(args, host) + if err != nil { + return portSettings, hash, err + } + + var requestUrl string + if isModel30x(model) { + requestUrl = fmt.Sprintf("http://%s/dashboard.cgi", host) + } else if isModel316(model) { + requestUrl = fmt.Sprintf("http://%s/iss/specific/dashboard.html", host) + } else { + panic("model not supported") + } + + dashboardData, err := requestPage(args, host, requestUrl) + if err != nil { + return portSettings, hash, err + } + + if checkIsLoginRequired(dashboardData) { + return portSettings, hash, errors.New("no content. please, (re-)login first") + } + + hash, err = findHashInHtml(model, strings.NewReader(dashboardData)) + if err != nil { + return portSettings, hash, err + } + + portSettings, err = findPortSettingsInHtml(model, strings.NewReader(dashboardData)) + + if err != nil { + return portSettings, hash, err + } + + return portSettings, hash, err + +} + +func requestPortSettingsUpdate(args *NtgrrcSession, host string, data string) (string, error) { + requestUrl := fmt.Sprintf("http://%s/port_status.cgi", host) + return postPage(args, host, requestUrl, data) +} diff --git a/port_set_test.go b/pkg/ntgrrc/port_set_test.go similarity index 77% rename from port_set_test.go rename to pkg/ntgrrc/port_set_test.go index 40b146f..c64d467 100644 --- a/port_set_test.go +++ b/pkg/ntgrrc/port_set_test.go @@ -1,6 +1,7 @@ -package main +package ntgrrc import ( + "github.com/nitram509/ntgrrc/pkg/ntgrrc/mapping" "strings" "testing" @@ -17,7 +18,7 @@ func TestFindHashInPortHtml(t *testing.T) { func TestComparePortSettingsUnknown(t *testing.T) { - for _, setting := range []Setting{Speed, IngressRateLimit, EgressRateLimit, FlowControl} { + for _, setting := range []setting{speedSetting, ingressRateLimitSetting, egressRateLimitSetting, flowControlSetting} { finalSetting, _ := comparePortSettings(setting, "defaultValue", "newValue") then.AssertThat(t, finalSetting, is.EqualTo("unknown").Reason("when providing a value that does not exist, return unknown to the caller")) @@ -34,59 +35,59 @@ func TestComparePortSettingsUnknown(t *testing.T) { func TestCompareSettingsSameName(t *testing.T) { - name, err := comparePortSettings(Name, "Port Name", "Port Name") + name, err := comparePortSettings(nameSetting, "Port Name", "Port Name") then.AssertThat(t, err, is.Nil()) then.AssertThat(t, name, is.EqualTo("Port Name")) } func TestCompareSettingsNameLengthLimit(t *testing.T) { - name, err := comparePortSettings(Name, "Port Name", "OK Port Name") + name, err := comparePortSettings(nameSetting, "Port Name", "OK Port Name") then.AssertThat(t, err, is.Nil()) then.AssertThat(t, name, is.EqualTo("OK Port Name").Reason("port names are allowed within a 16 character limit")) - name, err = comparePortSettings(Name, "Large Port Name", "Larger Port Name") + name, err = comparePortSettings(nameSetting, "Large Port Name", "Larger Port Name") then.AssertThat(t, err, is.Nil()) then.AssertThat(t, name, is.EqualTo("Larger Port Name").Reason("port names are allowed to be exactly 16 characters")) // Disallow new port names beyond 16 characters - name, err = comparePortSettings(Name, "Port Name", "Embiggened Port Name") + name, err = comparePortSettings(nameSetting, "Port Name", "Embiggened Port Name") then.AssertThat(t, err, is.Not(is.Nil())) - // Allow port names that are smaller and different than the current one - name, err = comparePortSettings(Name, "Larger Port Name", "New Port Name") + // Allow port names that are smaller and different from the current one + name, err = comparePortSettings(nameSetting, "Larger Port Name", "New Port Name") then.AssertThat(t, err, is.Nil()) then.AssertThat(t, name, is.EqualTo("New Port Name").Reason("port names are allowed to be changed")) // Name is allowed to be blank (unsetting the name for a port) - name, err = comparePortSettings(Name, "Port Name", "") + name, err = comparePortSettings(nameSetting, "Port Name", "") then.AssertThat(t, err, is.Nil()) then.AssertThat(t, name, is.EqualTo("").Reason("port names are allowed to be blank/unset")) } func TestCompareSettingsSpeed(t *testing.T) { - for key, value := range portSpeedMap { - result, err := comparePortSettings(Speed, value, value) + for key, value := range mapping.PortSpeedMap { + result, err := comparePortSettings(speedSetting, value, value) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, result, is.EqualTo(key).Reason("Key: "+key+" in portSpeedMap is expected to be value: "+value+" after comparePortSettings()")) } // Allow speed changes - speed, err := comparePortSettings(Speed, "1", "Disable") + speed, err := comparePortSettings(speedSetting, "1", "Disable") then.AssertThat(t, err, is.Nil()) then.AssertThat(t, speed, is.EqualTo("2").Reason("port speed index should be 2 if a change to Disable is requested")) // Check for an invalid speed and 'unknown' - speed, err = comparePortSettings(Speed, "invalid speed", "invalid speed") + speed, err = comparePortSettings(speedSetting, "invalid speed", "invalid speed") then.AssertThat(t, err, is.Not(is.Nil())) then.AssertThat(t, speed, is.EqualTo("unknown").Reason("invalid speeds should return an error message and be rejected")) } func TestCompareSettingsIngressEgress(t *testing.T) { - for _, setting := range []Setting{IngressRateLimit, EgressRateLimit} { - for key, value := range portRateLimitMap { + for _, setting := range []setting{ingressRateLimitSetting, egressRateLimitSetting} { + for key, value := range mapping.PortRateLimitMap { result, err := comparePortSettings(setting, value, value) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, result, is.EqualTo(key).Reason("Key: "+key+" in portRateLimitMap is expected to be value: "+value+" after comparePortSettings()")) @@ -106,19 +107,19 @@ func TestCompareSettingsIngressEgress(t *testing.T) { } func TestCompareSettingsFlowControl(t *testing.T) { - for key, value := range portFlowControlMap { - result, err := comparePortSettings(FlowControl, value, value) + for key, value := range mapping.PortFlowControlMap { + result, err := comparePortSettings(flowControlSetting, value, value) then.AssertThat(t, err, is.Nil()) then.AssertThat(t, result, is.EqualTo(key).Reason("Key: "+key+" in portFlowControlMap is expected to be value: "+value+" after comparePortSettings()")) } // Allow changes in port flow control - portFlowControl, err := comparePortSettings(FlowControl, "1", "Off") + portFlowControl, err := comparePortSettings(flowControlSetting, "1", "Off") then.AssertThat(t, err, is.Nil()) then.AssertThat(t, portFlowControl, is.EqualTo("2").Reason("flow control is allowed to be turned off")) // Check for invalid entry - portFlowControl, err = comparePortSettings(FlowControl, "invalid", "invalid") + portFlowControl, err = comparePortSettings(flowControlSetting, "invalid", "invalid") then.AssertThat(t, err, is.Not(is.Nil())) then.AssertThat(t, portFlowControl, is.EqualTo("unknown").Reason("an invalid flow control setting cannot be set")) diff --git a/port_settings.go b/pkg/ntgrrc/port_settings.go similarity index 59% rename from port_settings.go rename to pkg/ntgrrc/port_settings.go index 37d3115..6ec175e 100644 --- a/port_settings.go +++ b/pkg/ntgrrc/port_settings.go @@ -1,62 +1,18 @@ -package main +package ntgrrc import ( - "fmt" "github.com/PuerkitoBio/goquery" "io" "strconv" "strings" ) -type PortCommand struct { - PortSettingsCommand PortSettingsCommand `cmd:"" name:"settings" help:"show switch port settings" default:"1"` - PortSetCommand PortSetCommand `cmd:"" name:"set" help:"set properties for a port number"` -} - -type PortSettingsCommand struct { - Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` -} - -func (port *PortSettingsCommand) Run(args *GlobalOptions) error { - - settings, _, err := requestPortSettings(args, port.Address) +func (session *NtgrrcSession) GetPortSettings() ([]Port, error) { + settings, _, err := requestPortSettings(session, session.address) if err != nil { - return err - } - - prettyPrintPortSettings(args.OutputFormat, settings) - - return nil -} - -func prettyPrintPortSettings(format OutputFormat, settings []Port) { - - var header = []string{"Port ID", "Port Name", "Speed", "Ingress Limit", "Egress Limit", "Flow Control"} - var content [][]string - - for _, setting := range settings { - var row []string - row = append(row, fmt.Sprintf("%d", setting.Index)) - row = append(row, setting.Name) - setting.Speed = bidiMapLookup(setting.Speed, portSpeedMap) - row = append(row, setting.Speed) - setting.IngressRateLimit = bidiMapLookup(setting.IngressRateLimit, portRateLimitMap) - row = append(row, setting.IngressRateLimit) - setting.EgressRateLimit = bidiMapLookup(setting.EgressRateLimit, portRateLimitMap) - row = append(row, setting.EgressRateLimit) - setting.FlowControl = bidiMapLookup(setting.FlowControl, portFlowControlMap) - row = append(row, setting.FlowControl) - content = append(content, row) - } - switch format { - case MarkdownFormat: - printMarkdownTable(header, content) - case JsonFormat: - printJsonDataTable("port_settings", header, content) - default: - panic("not implemented format: " + format) + return nil, err } - + return settings, nil } func findPortSettingsInHtml(model NetgearModel, reader io.Reader) ([]Port, error) { diff --git a/port_settings_test.go b/pkg/ntgrrc/port_settings_test.go similarity index 99% rename from port_settings_test.go rename to pkg/ntgrrc/port_settings_test.go index 6d14d2e..d5a5d2b 100644 --- a/port_settings_test.go +++ b/pkg/ntgrrc/port_settings_test.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "github.com/corbym/gocrest/has" diff --git a/pkg/ntgrrc/print.go b/pkg/ntgrrc/print.go new file mode 100644 index 0000000..c6a5a64 --- /dev/null +++ b/pkg/ntgrrc/print.go @@ -0,0 +1,99 @@ +package ntgrrc + +import ( + "fmt" + "github.com/nitram509/ntgrrc/pkg/ntgrrc/mapping" +) + +type PrintFormat string + +const ( + MarkdownFormat PrintFormat = "md" + JsonFormat PrintFormat = "json" +) + +func PrettyPrintStatus(format PrintFormat, statuses []PoePortStatus) { + var header = []string{"Port ID", "Port Name", "Status", "PortPwr class", "Voltage (V)", "Current (mA)", "PortPwr (W)", "Temp. (°C)", "Error status"} + var content [][]string + for _, status := range statuses { + var row []string + row = append(row, fmt.Sprintf("%d", status.PortIndex)) + row = append(row, status.PortName) + row = append(row, status.PoePortStatus) + row = append(row, status.PoePowerClass) + row = append(row, fmt.Sprintf("%d", status.VoltageInVolt)) + row = append(row, fmt.Sprintf("%d", status.CurrentInMilliAmps)) + row = append(row, fmt.Sprintf("%.2f", status.PowerInWatt)) + row = append(row, fmt.Sprintf("%d", status.TemperatureInCelsius)) + row = append(row, status.ErrorStatus) + content = append(content, row) + } + switch format { + case MarkdownFormat: + printMarkdownTable(header, content) + case JsonFormat: + printJsonDataTable("poe_status", header, content) + default: + panic("not implemented format: " + format) + } +} + +func PrettyPrintPoePortSettings(format PrintFormat, settings []PoePortSetting) { + var header = []string{"Port ID", "Port Name", "Port Power", "Mode", "Priority", "Limit Type", "Limit (W)", "Type", "Longer Detection Time"} + var content [][]string + for _, setting := range settings { + var row []string + row = append(row, fmt.Sprintf("%d", setting.PortIndex)) + row = append(row, setting.PortName) + row = append(row, asTextPortPower(setting.PortPwr)) + row = append(row, mapping.BidiMapLookup(setting.PwrMode, mapping.PwrModeMap)) + row = append(row, mapping.BidiMapLookup(setting.PortPrio, mapping.PortPrioMap)) + row = append(row, mapping.BidiMapLookup(setting.LimitType, mapping.LimitTypeMap)) + row = append(row, setting.PwrLimit) + row = append(row, mapping.BidiMapLookup(setting.DetecType, mapping.DetecTypeMap)) + row = append(row, mapping.BidiMapLookup(setting.LongerDetect, mapping.LongerDetectMap)) + content = append(content, row) + } + switch format { + case MarkdownFormat: + printMarkdownTable(header, content) + case JsonFormat: + printJsonDataTable("poe_settings", header, content) + default: + panic("not implemented format: " + format) + } +} + +func asTextPortPower(portPwr bool) string { + if portPwr { + return "enabled" + } + return "disabled" +} + +func PrettyPrintPortSettings(format PrintFormat, settings []Port) { + var header = []string{"Port ID", "Port Name", "Speed", "Ingress Limit", "Egress Limit", "Flow Control"} + var content [][]string + for _, setting := range settings { + var row []string + row = append(row, fmt.Sprintf("%d", setting.Index)) + row = append(row, setting.Name) + setting.Speed = mapping.BidiMapLookup(setting.Speed, mapping.PortSpeedMap) + row = append(row, setting.Speed) + setting.IngressRateLimit = mapping.BidiMapLookup(setting.IngressRateLimit, mapping.PortRateLimitMap) + row = append(row, setting.IngressRateLimit) + setting.EgressRateLimit = mapping.BidiMapLookup(setting.EgressRateLimit, mapping.PortRateLimitMap) + row = append(row, setting.EgressRateLimit) + setting.FlowControl = mapping.BidiMapLookup(setting.FlowControl, mapping.PortFlowControlMap) + row = append(row, setting.FlowControl) + content = append(content, row) + } + switch format { + case MarkdownFormat: + printMarkdownTable(header, content) + case JsonFormat: + printJsonDataTable("port_settings", header, content) + default: + panic("not implemented format: " + format) + } +} diff --git a/formatter_json.go b/pkg/ntgrrc/print_json.go similarity index 97% rename from formatter_json.go rename to pkg/ntgrrc/print_json.go index e70aa95..188a5d3 100644 --- a/formatter_json.go +++ b/pkg/ntgrrc/print_json.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "fmt" diff --git a/formatter_markdown.go b/pkg/ntgrrc/print_markdown.go similarity index 98% rename from formatter_markdown.go rename to pkg/ntgrrc/print_markdown.go index 77ba74c..f46a462 100644 --- a/formatter_markdown.go +++ b/pkg/ntgrrc/print_markdown.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "strings" diff --git a/token.go b/pkg/ntgrrc/token.go similarity index 89% rename from token.go rename to pkg/ntgrrc/token.go index 2013971..c841363 100644 --- a/token.go +++ b/pkg/ntgrrc/token.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "errors" @@ -13,12 +13,12 @@ import ( const separator = ":" -func storeToken(args *GlobalOptions, host string, token string) error { +func storeToken(args *NtgrrcSession, host string, token string) error { err := ensureConfigPathExists(args.TokenDir) if err != nil { return err } - if args.Verbose { + if args.PrintVerbose { println("Storing login token " + tokenFilename(args.TokenDir, host)) } data := fmt.Sprintf("%s%s%s", args.model, separator, token) @@ -31,11 +31,11 @@ func tokenFilename(configDir string, host string) string { return filepath.Join(dotConfigDirName(configDir), "token-"+fmt.Sprintf("%x", hash32.Sum(nil))) } -func readTokenAndModel2GlobalOptions(args *GlobalOptions, host string) (NetgearModel, string, error) { +func readTokenAndModel2GlobalOptions(args *NtgrrcSession, host string) (NetgearModel, string, error) { if len(args.model) > 0 { return args.model, args.token, nil } - if args.Verbose { + if args.PrintVerbose { println("reading token from: " + tokenFilename(args.TokenDir, host)) } bytes, err := os.ReadFile(tokenFilename(args.TokenDir, host)) diff --git a/token_test.go b/pkg/ntgrrc/token_test.go similarity index 88% rename from token_test.go rename to pkg/ntgrrc/token_test.go index 822bdaa..c015421 100644 --- a/token_test.go +++ b/pkg/ntgrrc/token_test.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "github.com/corbym/gocrest/is" @@ -8,9 +8,9 @@ import ( func Test_storing_and_loading_a_token_also_preserves_the_model(t *testing.T) { // setup - args := GlobalOptions{ - Verbose: false, - model: GS30xEPx, + args := NtgrrcSession{ + PrintVerbose: false, + model: GS30xEPx, } const host = "ntgrrc-test-case-host" // given diff --git a/util.go b/pkg/ntgrrc/util.go similarity index 82% rename from util.go rename to pkg/ntgrrc/util.go index 06d3f5e..23cb160 100644 --- a/util.go +++ b/pkg/ntgrrc/util.go @@ -1,4 +1,4 @@ -package main +package ntgrrc import ( "errors" @@ -6,13 +6,6 @@ import ( "strings" ) -func max(a int, b int) int { - if b > a { - return b - } - return a -} - func suffixToLength(s string, length int) string { if len(s) < length { diff := length - len(s) @@ -31,7 +24,7 @@ func parseInt32(text string) int32 { return int32(i64) } -func ensureModelIs30x(args *GlobalOptions, host string) error { +func ensureModelIs30x(args *NtgrrcSession, host string) error { model, _, err := readTokenAndModel2GlobalOptions(args, host) if err != nil { return err diff --git a/pkg/ntgrrc/util_test.go b/pkg/ntgrrc/util_test.go new file mode 100644 index 0000000..b4e1521 --- /dev/null +++ b/pkg/ntgrrc/util_test.go @@ -0,0 +1,18 @@ +package ntgrrc + +import ( + "github.com/corbym/gocrest/is" + "github.com/corbym/gocrest/then" + "testing" +) + +func TestSuffixToLength(t *testing.T) { + s := suffixToLength("123", 5) + then.AssertThat(t, s, is.EqualTo("123 ")) + + s = suffixToLength("12345", 5) + then.AssertThat(t, s, is.EqualTo("12345")) + + s = suffixToLength("12345", 3) + then.AssertThat(t, s, is.EqualTo("12345")) +} diff --git a/poe_cycle.go b/poe_cycle.go deleted file mode 100644 index eecc7a7..0000000 --- a/poe_cycle.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "net/url" -) - -type PoeCyclePowerCommand struct { - Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` - Ports []int `required:"" help:"port number (starting with 1), use multiple times for cycling multiple ports at once" short:"p" name:"port"` -} - -func (poe *PoeCyclePowerCommand) Run(args *GlobalOptions) error { - err := ensureModelIs30x(args, poe.Address) - if err != nil { - return err - } - - poeExt := &PoeExt{} - - settings, err := requestPoeConfiguration(args, poe.Address, poeExt) - if err != nil { - return err - } - - poeSettings := url.Values{ - "hash": {poeExt.Hash}, - "ACTION": {"Reset"}, - } - - for _, switchPort := range poe.Ports { - if switchPort > len(settings) || switchPort < 1 { - return errors.New(fmt.Sprintf("given port id %d, doesn't fit in range 1..%d", switchPort, len(settings))) - } - poeSettings.Add(fmt.Sprintf("port%d", switchPort-1), "checked") - } - - result, err := requestPoeSettingsUpdate(args, poe.Address, poeSettings.Encode()) - if result != "SUCCESS" { - return errors.New(result) - } - if err != nil { - return err - } - - settings, err = requestPoeConfiguration(args, poe.Address, poeExt) - if err != nil { - return err - } - - changedPorts := collectChangedPoePortConfiguration(poe.Ports, settings) - prettyPrintSettings(args.OutputFormat, changedPorts) - return nil -} diff --git a/poe_settings.go b/poe_settings.go deleted file mode 100644 index c633a5e..0000000 --- a/poe_settings.go +++ /dev/null @@ -1,123 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "io" - "strconv" - "strings" - - "github.com/PuerkitoBio/goquery" -) - -type PoePortSetting struct { - PortIndex int8 - PortName string - PortPwr bool - PwrMode string - PortPrio string - LimitType string - PwrLimit string - DetecType string - LongerDetect string -} - -type PoeShowSettingsCommand struct { - Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` -} - -func (poe *PoeShowSettingsCommand) Run(args *GlobalOptions) error { - err := ensureModelIs30x(args, poe.Address) - if err != nil { - return err - } - - settingsPage, err := requestPoePortConfigPage(args, poe.Address) - if err != nil { - return err - } - if checkIsLoginRequired(settingsPage) { - return errors.New("no content. please, (re-)login first") - } - var settings []PoePortSetting - settings, err = findPoeSettingsInHtml(strings.NewReader(settingsPage)) - if err != nil { - return err - } - prettyPrintSettings(args.OutputFormat, settings) - return nil -} - -func prettyPrintSettings(format OutputFormat, settings []PoePortSetting) { - var header = []string{"Port ID", "Port Name", "Port Power", "Mode", "Priority", "Limit Type", "Limit (W)", "Type", "Longer Detection Time"} - var content [][]string - for _, setting := range settings { - var row []string - row = append(row, fmt.Sprintf("%d", setting.PortIndex)) - row = append(row, setting.PortName) - row = append(row, asTextPortPower(setting.PortPwr)) - row = append(row, bidiMapLookup(setting.PwrMode, pwrModeMap)) - row = append(row, bidiMapLookup(setting.PortPrio, portPrioMap)) - row = append(row, bidiMapLookup(setting.LimitType, limitTypeMap)) - row = append(row, setting.PwrLimit) - row = append(row, bidiMapLookup(setting.DetecType, detecTypeMap)) - row = append(row, bidiMapLookup(setting.LongerDetect, longerDetectMap)) - content = append(content, row) - } - switch format { - case MarkdownFormat: - printMarkdownTable(header, content) - case JsonFormat: - printJsonDataTable("poe_settings", header, content) - default: - panic("not implemented format: " + format) - } -} - -func asTextPortPower(portPwr bool) string { - if portPwr { - return "enabled" - } - return "disabled" -} - -func requestPoePortConfigPage(args *GlobalOptions, host string) (string, error) { - url := fmt.Sprintf("http://%s/PoEPortConfig.cgi", host) - return requestPage(args, host, url) -} - -func findPoeSettingsInHtml(reader io.Reader) ([]PoePortSetting, error) { - doc, err := goquery.NewDocumentFromReader(reader) - if err != nil { - return nil, err - } - - var configs []PoePortSetting - doc.Find("li.poePortSettingListItem").Each(func(i int, s *goquery.Selection) { - config := PoePortSetting{} - - id, _ := s.Find("input[type=hidden].port").Attr("value") - var id64, _ = strconv.ParseInt(id, 10, 8) - config.PortIndex = int8(id64) - - config.PortName, _ = s.Find("input[type=hidden].portName").Attr("value") - - portWr, exists := s.Find("input#hidPortPwr").Attr("value") - config.PortPwr = exists && portWr == "1" - - config.PwrMode, _ = s.Find("input#hidPwrMode").Attr("value") - - config.PortPrio, _ = s.Find("input#hidPortPrio").Attr("value") - - config.LimitType, _ = s.Find("input#hidLimitType").Attr("value") - - config.PwrLimit, _ = s.Find("input.pwrLimit").Attr("value") - - config.DetecType, _ = s.Find("input#hidDetecType").Attr("value") - - config.LongerDetect, _ = s.Find("input.longerDetect").Attr("value") - - configs = append(configs, config) - }) - return configs, nil -} diff --git a/port_set.go b/port_set.go deleted file mode 100644 index 035ad14..0000000 --- a/port_set.go +++ /dev/null @@ -1,221 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "net/url" - "strings" -) - -type PortSetting string - -const ( - Index Setting = "Index" - Name Setting = "Name" - Speed Setting = "Speed" - IngressRateLimit Setting = "IngressRateLimit" - EgressRateLimit Setting = "EgressRateLimit" - FlowControl Setting = "FlowControl" -) - -type Port struct { - Index int8 - Name string - Speed string - IngressRateLimit string - EgressRateLimit string - FlowControl string -} - -type PortSetCommand struct { - Address string `required:"" help:"the Netgear switch's IP address or host name to connect to" short:"a"` - Ports []int `required:"" help:"port number (starting with 1), use multiple times for setting multiple ports at once" short:"p" name:"port"` - Name *string `optional:"" help:"sets the name of a port, 1-16 character limit" short:"n"` - Speed string `optional:"" help:"set the speed and duplex of the port ['100M full', '100M half', '10M full', '10M half', 'Auto', 'Disable']" short:"s"` - IngressRateLimit string `optional:"" help:"set an incoming rate limit for the port ['1 Mbit/s', '128 Mbit/s', '16 Mbit/s', '2 Mbit/s', '256 Mbit/s', '32 Mbit/s', '4 Mbit/s', '512 Kbit/s', '512 Mbit/s', '64 Mbit/s', '8 Mbit/s', 'No Limit']" short:"i"` - EgressRateLimit string `optional:"" help:"set an outgoing rate limit for the port ['1 Mbit/s', '128 Mbit/s', '16 Mbit/s', '2 Mbit/s', '256 Mbit/s', '32 Mbit/s', '4 Mbit/s', '512 Kbit/s', '512 Mbit/s', '64 Mbit/s', '8 Mbit/s', 'No Limit']" short:"o"` - FlowControl string `optional:"" help:"enable/disable flow control on port ['Off', 'On']"` -} - -func (portSet *PortSetCommand) Run(args *GlobalOptions) error { - settings, hash, err := requestPortSettings(args, portSet.Address) - if err != nil { - return err - } - - err = ensureModelIs30x(args, portSet.Address) - if err != nil { - return err - } - - for _, switchPort := range portSet.Ports { - - if switchPort > len(settings) || switchPort < 1 { - return errors.New(fmt.Sprintf("given port id %d, doesn't fit in range 1..%d", switchPort, len(settings))) - } - - portSetting := settings[switchPort-1] - - // If the port name was not set by the user, set it to the existing name (otherwise an empty port name is always considered to be the - // "new" value which blanks the port name on the setting next update) - if portSet.Name == nil { - portSet.Name = &portSetting.Name - } - - name, err := comparePortSettings(Name, portSetting.Name, *portSet.Name) - if err != nil { - return err - } - - speed, err := comparePortSettings(Speed, portSetting.Speed, portSet.Speed) - if err != nil { - return err - } - - inRateLimit, err := comparePortSettings(IngressRateLimit, portSetting.IngressRateLimit, portSet.IngressRateLimit) - if err != nil { - return err - } - - outRateLimit, err := comparePortSettings(EgressRateLimit, portSetting.EgressRateLimit, portSet.EgressRateLimit) - if err != nil { - return err - } - - flowControl, err := comparePortSettings(FlowControl, portSetting.FlowControl, portSet.FlowControl) - if err != nil { - return err - } - - portUpdateValues := url.Values{ - "hash": {hash}, - fmt.Sprintf("%s%d", "port", portSetting.Index): {"checked"}, - "SPEED": {speed}, - "FLOW_CONTROL": {flowControl}, - "DESCRIPTION": {name}, - "IngressRate": {inRateLimit}, - "EgressRate": {outRateLimit}, - "priority": {"0"}, - } - - result, err := requestPortSettingsUpdate(args, portSet.Address, portUpdateValues.Encode()) - if err != nil { - return err - } - - if result != "SUCCESS" { - return errors.New(result) - } - } - - settings, _, err = requestPortSettings(args, portSet.Address) - if err != nil { - return err - } - - changedPorts := collectChangedPortConfiguration(portSet.Ports, settings) - prettyPrintPortSettings(args.OutputFormat, changedPorts) - - return err -} - -func collectChangedPortConfiguration(ports []int, settings []Port) (changedPorts []Port) { - for _, configuredPort := range ports { - for _, portSetting := range settings { - if int(portSetting.Index) == configuredPort { - changedPorts = append(changedPorts, portSetting) - } - } - } - - return changedPorts -} - -func comparePortSettings(name Setting, defaultValue string, newValue string) (string, error) { - if len(newValue) == 0 && name != Name { - return defaultValue, nil - } - - switch name { - case Name: - if defaultValue != newValue { - if len(newValue) <= 16 { - return newValue, nil - } else { - return defaultValue, errors.New("port name could not be set. Port name must be 16 characters or less") - } - } - return defaultValue, nil - case Speed: - speed := bidiMapLookup(newValue, portSpeedMap) - if speed == "unknown" { - return speed, errors.New("port speed could not be set. Accepted values are: " + valuesAsString(portSpeedMap)) - } - return speed, nil - case IngressRateLimit: - inRateLimit := bidiMapLookup(newValue, portRateLimitMap) - if inRateLimit == "unknown" { - return inRateLimit, errors.New("ingress rate limit could not be set. Accepted values are: " + valuesAsString(portRateLimitMap)) - } - return inRateLimit, nil - case EgressRateLimit: - outRateLimit := bidiMapLookup(newValue, portRateLimitMap) - if outRateLimit == "unknown" { - return outRateLimit, errors.New("egress rate limit could not be set. Accepted values are: " + valuesAsString(portRateLimitMap)) - } - return outRateLimit, nil - case FlowControl: - flowControl := bidiMapLookup(newValue, portFlowControlMap) - if flowControl == "unknown" { - return flowControl, errors.New("flow control could not be set. Accepted values are: " + valuesAsString(portFlowControlMap)) - } - return flowControl, nil - default: - return defaultValue, errors.New("could not find port setting") - } - -} - -func requestPortSettings(args *GlobalOptions, host string) (portSettings []Port, hash string, err error) { - model, _, err := readTokenAndModel2GlobalOptions(args, host) - if err != nil { - return portSettings, hash, err - } - - var requestUrl string - if isModel30x(model) { - requestUrl = fmt.Sprintf("http://%s/dashboard.cgi", host) - } else if isModel316(model) { - requestUrl = fmt.Sprintf("http://%s/iss/specific/dashboard.html", host) - } else { - panic("model not supported") - } - - dashboardData, err := requestPage(args, host, requestUrl) - if err != nil { - return portSettings, hash, err - } - - if checkIsLoginRequired(dashboardData) { - return portSettings, hash, errors.New("no content. please, (re-)login first") - } - - hash, err = findHashInHtml(model, strings.NewReader(dashboardData)) - if err != nil { - return portSettings, hash, err - } - - portSettings, err = findPortSettingsInHtml(model, strings.NewReader(dashboardData)) - - if err != nil { - return portSettings, hash, err - } - - return portSettings, hash, err - -} - -func requestPortSettingsUpdate(args *GlobalOptions, host string, data string) (string, error) { - requestUrl := fmt.Sprintf("http://%s/port_status.cgi", host) - return postPage(args, host, requestUrl, data) -} From 353b59873ec5378bd5852626d28c2e10c0da1a02 Mon Sep 17 00:00:00 2001 From: nitram509 Date: Tue, 14 May 2024 23:29:55 +0200 Subject: [PATCH 4/6] fix test setup --- pkg/ntgrrc/example_setup_test.go | 37 ++++++++++++++++++++++++++++++++ pkg/ntgrrc/example_test.go | 19 ++++++++++------ 2 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 pkg/ntgrrc/example_setup_test.go diff --git a/pkg/ntgrrc/example_setup_test.go b/pkg/ntgrrc/example_setup_test.go new file mode 100644 index 0000000..b1d3a36 --- /dev/null +++ b/pkg/ntgrrc/example_setup_test.go @@ -0,0 +1,37 @@ +package ntgrrc + +import ( + "fmt" + "log" + "net" + "net/http" + "os" + "testing" +) + +var mockServerPort int + +func TestMain(m *testing.M) { + setupGs305EPMockServer() + exitVal := m.Run() + os.Exit(exitVal) +} + +func setupGs305EPMockServer() { + http.HandleFunc("/", alwaysReturn200Ok) + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + panic(err) + } + mockServerPort = listener.Addr().(*net.TCPAddr).Port + log.Println(fmt.Sprintf("init GS316EP mock server on port 127.0.0.1:%d", mockServerPort)) + go http.Serve(listener, nil) + if err != nil { + panic(err) + } +} + +func alwaysReturn200Ok(w http.ResponseWriter, r *http.Request) { + html := loadTestFile(string(GS316EP), "_root.html") + _, _ = w.Write([]byte(html)) +} diff --git a/pkg/ntgrrc/example_test.go b/pkg/ntgrrc/example_test.go index 72cfdf1..193b9f8 100644 --- a/pkg/ntgrrc/example_test.go +++ b/pkg/ntgrrc/example_test.go @@ -1,22 +1,27 @@ package ntgrrc +import "fmt" + func ExampleNtgrrcSession_DoLogin() { - host := "127.0.0.1" // the IP address or host name + host := "127.0.0.1" // the IP address or host name + host = host + fmt.Sprintf(":%d", mockServerPort) // only required in unit test context passw := "secret" session := NewSession() err := session.DoLogin(host, passw) if err != nil { - panic(err) + println(err) } } func ExampleNtgrrcSession_DetectNetgearModel() { - host := "127.0.0.1" // the IP address or host name - session := NewSession() + host := "127.0.0.1" // the IP address or host name + host = host + fmt.Sprintf(":%d", mockServerPort) // only required in unit test context + session := NewSession() // for detecting the model, you don't need a password model, err := session.DetectNetgearModel(host) if err != nil { - panic(err) + println(err) } - println(model) - // Output: GS308EPP + fmt.Print(model) + + // Output: GS316EP } From f533e31d19186723d85d6ee86efb93f09f6dfdc8 Mon Sep 17 00:00:00 2001 From: nitram509 Date: Wed, 12 Jun 2024 00:00:14 +0200 Subject: [PATCH 5/6] more cosmetic refactorings --- cmd/ntgrrc/poe_status.go | 2 +- pkg/ntgrrc/example_test.go | 16 ++++++++++++++++ pkg/ntgrrc/netgear_model.go | 1 + pkg/ntgrrc/poe_status.go | 8 ++++---- pkg/ntgrrc/poe_status_test.go | 4 ++-- pkg/ntgrrc/print.go | 3 ++- 6 files changed, 26 insertions(+), 8 deletions(-) diff --git a/cmd/ntgrrc/poe_status.go b/cmd/ntgrrc/poe_status.go index 6d0ff16..2865eed 100644 --- a/cmd/ntgrrc/poe_status.go +++ b/cmd/ntgrrc/poe_status.go @@ -15,6 +15,6 @@ func (poe *PoeStatusCommand) Run(args *CliOptions) error { } status, err := session.GetPoePortStatus() - ntgrrc.PrettyPrintStatus(args.OutputFormat, status) + ntgrrc.PrettyPrintPoePortStatus(args.OutputFormat, status) return err } diff --git a/pkg/ntgrrc/example_test.go b/pkg/ntgrrc/example_test.go index 193b9f8..eddec0b 100644 --- a/pkg/ntgrrc/example_test.go +++ b/pkg/ntgrrc/example_test.go @@ -25,3 +25,19 @@ func ExampleNtgrrcSession_DetectNetgearModel() { // Output: GS316EP } + +func ExampleNtgrrcSession_GetPoePortStatus() { + host := "127.0.0.1" // the IP address or host name + host = host + fmt.Sprintf(":%d", mockServerPort) // only required in unit test context + passw := "secret" + session := NewSession() + err := session.DoLogin(host, passw) + if err != nil { + println(err) + } + status, err := session.GetPoePortStatus() + if err != nil { + println(err) + } + PrettyPrintPoePortStatus(MarkdownFormat, status) // print all status items +} diff --git a/pkg/ntgrrc/netgear_model.go b/pkg/ntgrrc/netgear_model.go index 2a1c437..12eb12d 100644 --- a/pkg/ntgrrc/netgear_model.go +++ b/pkg/ntgrrc/netgear_model.go @@ -20,6 +20,7 @@ const ( GS316EPP NetgearModel = "GS316EPP" ) +// DetectNetgearModel connects to given IP or host name and tries to identify the Netgear switch model func (session *NtgrrcSession) DetectNetgearModel(host string) (NetgearModel, error) { url := fmt.Sprintf("http://%s/", host) if session.PrintVerbose { diff --git a/pkg/ntgrrc/poe_status.go b/pkg/ntgrrc/poe_status.go index 6dff3bd..40f683f 100644 --- a/pkg/ntgrrc/poe_status.go +++ b/pkg/ntgrrc/poe_status.go @@ -38,18 +38,18 @@ func (session *NtgrrcSession) GetPoePortStatus() ([]PoePortStatus, error) { return statuses, nil } -func requestPoePortStatusPage(args *NtgrrcSession, host string) (string, error) { - model, _, err := readTokenAndModel2GlobalOptions(args, host) +func requestPoePortStatusPage(session *NtgrrcSession, host string) (string, error) { + model, _, err := readTokenAndModel2GlobalOptions(session, host) if err != nil { return "", err } if isModel30x(model) { url := fmt.Sprintf("http://%s/getPoePortStatus.cgi", host) - return requestPage(args, host, url) + return requestPage(session, host, url) } if isModel316(model) { url := fmt.Sprintf("http://%s/iss/specific/poePortStatus.html?GetData=TRUE", host) - return requestPage(args, host, url) + return requestPage(session, host, url) } panic("model not supported") } diff --git a/pkg/ntgrrc/poe_status_test.go b/pkg/ntgrrc/poe_status_test.go index ed1e4e5..6c812a0 100644 --- a/pkg/ntgrrc/poe_status_test.go +++ b/pkg/ntgrrc/poe_status_test.go @@ -119,7 +119,7 @@ func TestPrettyPrintMarkdownStatus(t *testing.T) { then.AssertThat(t, err, is.Nil()) then.AssertThat(t, statuses, has.Length[PoePortStatus](test.expectedVal)) - PrettyPrintStatus(MarkdownFormat, statuses) + PrettyPrintPoePortStatus(MarkdownFormat, statuses) }) } } @@ -153,7 +153,7 @@ func TestPrettyPrintJsonStatus(t *testing.T) { then.AssertThat(t, err, is.Nil()) then.AssertThat(t, statuses, has.Length[PoePortStatus](test.expectedVal)) - PrettyPrintStatus(JsonFormat, statuses) + PrettyPrintPoePortStatus(JsonFormat, statuses) }) } } diff --git a/pkg/ntgrrc/print.go b/pkg/ntgrrc/print.go index c6a5a64..ccfae21 100644 --- a/pkg/ntgrrc/print.go +++ b/pkg/ntgrrc/print.go @@ -12,7 +12,8 @@ const ( JsonFormat PrintFormat = "json" ) -func PrettyPrintStatus(format PrintFormat, statuses []PoePortStatus) { +// PrettyPrintPoePortStatus prints Markdown or JSON information of the PoePortStatus list items +func PrettyPrintPoePortStatus(format PrintFormat, statuses []PoePortStatus) { var header = []string{"Port ID", "Port Name", "Status", "PortPwr class", "Voltage (V)", "Current (mA)", "PortPwr (W)", "Temp. (°C)", "Error status"} var content [][]string for _, status := range statuses { From f8988662de0f3665a4758b0a1877b62dc0f6bcfa Mon Sep 17 00:00:00 2001 From: nitram509 Date: Wed, 12 Jun 2024 00:31:12 +0200 Subject: [PATCH 6/6] fix wrong host used --- pkg/ntgrrc/example_test.go | 19 +++++++++++++++---- pkg/ntgrrc/login.go | 22 +++++++++++----------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/pkg/ntgrrc/example_test.go b/pkg/ntgrrc/example_test.go index eddec0b..81fac19 100644 --- a/pkg/ntgrrc/example_test.go +++ b/pkg/ntgrrc/example_test.go @@ -9,8 +9,10 @@ func ExampleNtgrrcSession_DoLogin() { session := NewSession() err := session.DoLogin(host, passw) if err != nil { - println(err) + fmt.Println(err) } + + // Output: } func ExampleNtgrrcSession_DetectNetgearModel() { @@ -19,7 +21,7 @@ func ExampleNtgrrcSession_DetectNetgearModel() { session := NewSession() // for detecting the model, you don't need a password model, err := session.DetectNetgearModel(host) if err != nil { - println(err) + fmt.Println(err) } fmt.Print(model) @@ -33,11 +35,20 @@ func ExampleNtgrrcSession_GetPoePortStatus() { session := NewSession() err := session.DoLogin(host, passw) if err != nil { - println(err) + fmt.Println(err) } status, err := session.GetPoePortStatus() if err != nil { - println(err) + fmt.Println(err) + } + + // print out all port names + for _, portStatus := range status { + fmt.Println(portStatus.PortIndex, portStatus.PortName) } + + // convenient helper method to print all status PrettyPrintPoePortStatus(MarkdownFormat, status) // print all status items + + // Output: } diff --git a/pkg/ntgrrc/login.go b/pkg/ntgrrc/login.go index 8b68ea3..6e1bc18 100644 --- a/pkg/ntgrrc/login.go +++ b/pkg/ntgrrc/login.go @@ -28,7 +28,7 @@ func (session *NtgrrcSession) DoLogin(address string, password string) error { encryptedPwd := encryptPassword(password, seedValue) - err = doLoginRequest(session, password, encryptedPwd) + err = doLoginRequest(session, address, encryptedPwd) if err != nil { return err } @@ -37,23 +37,23 @@ func (session *NtgrrcSession) DoLogin(address string, password string) error { return nil } -func doLoginRequest(args *NtgrrcSession, host string, encryptedPwd string) error { +func doLoginRequest(session *NtgrrcSession, host string, encryptedPwd string) error { var url string - if isModel30x(args.model) { + if isModel30x(session.model) { url = fmt.Sprintf("http://%s/login.cgi", host) - } else if isModel316(args.model) { + } else if isModel316(session.model) { url = fmt.Sprintf("http://%s/redirect.html", host) } else { return errors.New("Unknown model not supported, please contact the developers ") } - if args.PrintVerbose { + if session.PrintVerbose { println("login attempt: " + url) } var formData string - if isModel30x(args.model) { + if isModel30x(session.model) { formData = "password=" + encryptedPwd - } else if isModel316(args.model) { + } else if isModel316(session.model) { formData = "LoginPassword=" + encryptedPwd } @@ -62,7 +62,7 @@ func doLoginRequest(args *NtgrrcSession, host string, encryptedPwd string) error return err } defer resp.Body.Close() - if args.PrintVerbose { + if session.PrintVerbose { println(resp.Status) } body, err := io.ReadAll(resp.Body) @@ -71,21 +71,21 @@ func doLoginRequest(args *NtgrrcSession, host string, encryptedPwd string) error } var token string - if isModel30x(args.model) { + if isModel30x(session.model) { token = getSessionToken(resp) if token == FailedAttempt && resp.StatusCode == http.StatusOK { return errors.New("login request returned 200 OK, but response did not contain a session token ('SID' cookie). " + "this is known behaviour from the switch. please, wait some minutes and tray again later") } } - if isModel316(args.model) { + if isModel316(session.model) { token = findGambitTokenInResponseHtml(strings.NewReader(string(body))) if token == FailedAttempt && resp.StatusCode == http.StatusOK { return errors.New("login request returned 200 OK, but response did not contain a token ('Gambit' value in input field) ") } } - err = storeToken(args, host, token) + err = storeToken(session, host, token) if err != nil { return err }