From 44750be39c2799d499aad70566f4083a06387acf Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Tue, 9 Apr 2024 07:21:38 +0200 Subject: [PATCH 01/27] fix: the module path --- cmd/ovp8xx/cmd/diagnostic.go | 2 +- cmd/ovp8xx/cmd/factoryReset.go | 2 +- cmd/ovp8xx/cmd/get.go | 2 +- cmd/ovp8xx/cmd/getSchema.go | 2 +- cmd/ovp8xx/cmd/getinit.go | 2 +- cmd/ovp8xx/cmd/pcic.go | 2 +- cmd/ovp8xx/cmd/reboot.go | 2 +- cmd/ovp8xx/cmd/root.go | 2 +- cmd/ovp8xx/cmd/saveinit.go | 2 +- cmd/ovp8xx/cmd/set.go | 2 +- cmd/ovp8xx/ovp8xx.go | 2 +- go.mod | 2 +- pkg/pcic/chunk_test.go | 2 +- pkg/pcic/protocol_test.go | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cmd/ovp8xx/cmd/diagnostic.go b/cmd/ovp8xx/cmd/diagnostic.go index 91fa58b..3ae0c9e 100644 --- a/cmd/ovp8xx/cmd/diagnostic.go +++ b/cmd/ovp8xx/cmd/diagnostic.go @@ -6,7 +6,7 @@ package cmd import ( "fmt" - "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" "github.com/spf13/cobra" ) diff --git a/cmd/ovp8xx/cmd/factoryReset.go b/cmd/ovp8xx/cmd/factoryReset.go index ae40222..e773fb5 100644 --- a/cmd/ovp8xx/cmd/factoryReset.go +++ b/cmd/ovp8xx/cmd/factoryReset.go @@ -4,7 +4,7 @@ Copyright © 2023 Christian Ege package cmd import ( - "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" "github.com/spf13/cobra" ) diff --git a/cmd/ovp8xx/cmd/get.go b/cmd/ovp8xx/cmd/get.go index c11cc14..fc4d7cf 100644 --- a/cmd/ovp8xx/cmd/get.go +++ b/cmd/ovp8xx/cmd/get.go @@ -4,7 +4,7 @@ Copyright © 2023 Christian Ege package cmd import ( - "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" "github.com/spf13/cobra" ) diff --git a/cmd/ovp8xx/cmd/getSchema.go b/cmd/ovp8xx/cmd/getSchema.go index a5a27e8..0baa643 100644 --- a/cmd/ovp8xx/cmd/getSchema.go +++ b/cmd/ovp8xx/cmd/getSchema.go @@ -4,7 +4,7 @@ Copyright © 2023 Christian Ege package cmd import ( - "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" "github.com/spf13/cobra" ) diff --git a/cmd/ovp8xx/cmd/getinit.go b/cmd/ovp8xx/cmd/getinit.go index 7bb70a9..08b7238 100644 --- a/cmd/ovp8xx/cmd/getinit.go +++ b/cmd/ovp8xx/cmd/getinit.go @@ -4,7 +4,7 @@ Copyright © 2023 Christian Ege package cmd import ( - "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" "github.com/spf13/cobra" ) diff --git a/cmd/ovp8xx/cmd/pcic.go b/cmd/ovp8xx/cmd/pcic.go index 3ad989c..e427a31 100644 --- a/cmd/ovp8xx/cmd/pcic.go +++ b/cmd/ovp8xx/cmd/pcic.go @@ -6,7 +6,7 @@ package cmd import ( "fmt" - "github.com/graugans/go-ovp8xx/pkg/pcic" + "github.com/graugans/go-ovp8xx/v2/pkg/pcic" "github.com/spf13/cobra" ) diff --git a/cmd/ovp8xx/cmd/reboot.go b/cmd/ovp8xx/cmd/reboot.go index 8dd17a1..d02da30 100644 --- a/cmd/ovp8xx/cmd/reboot.go +++ b/cmd/ovp8xx/cmd/reboot.go @@ -4,7 +4,7 @@ Copyright © 2023 Christian Ege package cmd import ( - "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" "github.com/spf13/cobra" ) diff --git a/cmd/ovp8xx/cmd/root.go b/cmd/ovp8xx/cmd/root.go index 2d2353a..dce12e1 100644 --- a/cmd/ovp8xx/cmd/root.go +++ b/cmd/ovp8xx/cmd/root.go @@ -7,7 +7,7 @@ import ( "fmt" "os" - "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" "github.com/spf13/cobra" ) diff --git a/cmd/ovp8xx/cmd/saveinit.go b/cmd/ovp8xx/cmd/saveinit.go index 5cc691d..d46667d 100644 --- a/cmd/ovp8xx/cmd/saveinit.go +++ b/cmd/ovp8xx/cmd/saveinit.go @@ -4,7 +4,7 @@ Copyright © 2023 Christian Ege package cmd import ( - "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" "github.com/spf13/cobra" ) diff --git a/cmd/ovp8xx/cmd/set.go b/cmd/ovp8xx/cmd/set.go index 9fbd331..ac9528f 100644 --- a/cmd/ovp8xx/cmd/set.go +++ b/cmd/ovp8xx/cmd/set.go @@ -8,7 +8,7 @@ import ( "fmt" "io" - "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" "github.com/spf13/cobra" ) diff --git a/cmd/ovp8xx/ovp8xx.go b/cmd/ovp8xx/ovp8xx.go index b78a9fc..89e8b00 100644 --- a/cmd/ovp8xx/ovp8xx.go +++ b/cmd/ovp8xx/ovp8xx.go @@ -1,7 +1,7 @@ package main import ( - "github.com/graugans/go-ovp8xx/cmd/ovp8xx/cmd" + "github.com/graugans/go-ovp8xx/v2/cmd/ovp8xx/cmd" ) var ( diff --git a/go.mod b/go.mod index d1f12aa..61cf001 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/graugans/go-ovp8xx +module github.com/graugans/go-ovp8xx/v2 go 1.21 diff --git a/pkg/pcic/chunk_test.go b/pkg/pcic/chunk_test.go index 7636f22..e2c9008 100644 --- a/pkg/pcic/chunk_test.go +++ b/pkg/pcic/chunk_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/graugans/go-ovp8xx/pkg/pcic" + "github.com/graugans/go-ovp8xx/v2/pkg/pcic" "github.com/stretchr/testify/assert" ) diff --git a/pkg/pcic/protocol_test.go b/pkg/pcic/protocol_test.go index 06c0fcc..c3d3ef6 100644 --- a/pkg/pcic/protocol_test.go +++ b/pkg/pcic/protocol_test.go @@ -10,7 +10,7 @@ import ( "strings" "testing" - "github.com/graugans/go-ovp8xx/pkg/pcic" + "github.com/graugans/go-ovp8xx/v2/pkg/pcic" "github.com/stretchr/testify/assert" ) From 043a98c64a82b84c3af4add9acbf1403bf001cf5 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Tue, 9 Apr 2024 07:57:55 +0200 Subject: [PATCH 02/27] ci: run perform go-semantic-release on v2 Signed-off-by: Christian Ege --- .semrelrc | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .semrelrc diff --git a/.semrelrc b/.semrelrc new file mode 100644 index 0000000..32a2663 --- /dev/null +++ b/.semrelrc @@ -0,0 +1,3 @@ +{ + "maintainedVersion": "2" +} \ No newline at end of file From c7f6cd9c7974af5522b92a2a983204c73a2f5e1c Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Tue, 9 Apr 2024 07:59:39 +0200 Subject: [PATCH 03/27] ci: switch to the latest go version 1.22 Signed-off-by: Christian Ege --- .github/workflows/go.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8e06048..e1dfc3f 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.22' cache: false - name: golangci-lint uses: golangci/golangci-lint-action@v3 @@ -38,7 +38,7 @@ jobs: - name: 👷 Prepare the Go environment uses: actions/setup-go@v4 with: - go-version: '1.21' + go-version: '1.22' - name: 🧪 Run the unit tests run: go test -v -failfast -coverprofile cover.out -timeout=1m ./... - name: 🚀 Upload the coverage reports to Codecov @@ -52,7 +52,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: '1.21' + go-version: '1.22' - name: Install dependencies run: go get -d ./... @@ -72,7 +72,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: '1.21' + go-version: '1.22' - uses: go-semantic-release/action@v1 with: hooks: goreleaser From 41cfd85dcc39ae9742e3435be7edcd1c5388ce08 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Tue, 9 Apr 2024 08:09:45 +0200 Subject: [PATCH 04/27] ci: bump the ci lint version to v1.57 Signed-off-by: Christian Ege --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e1dfc3f..7e33c4b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -25,7 +25,7 @@ jobs: # Require: The version of golangci-lint to use. # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. - version: v1.53 + version: v1.57 test: runs-on: ubuntu-latest From 9c645a2897e37eca353146a9dd56322ff4f687fe Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Tue, 9 Apr 2024 08:33:32 +0200 Subject: [PATCH 05/27] doc: update the README Signed-off-by: Christian Ege --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 14df857..798be81 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ The recommended and easiest way is to download the pre-build binary from the [Gi If you have a decent Go version installed ```sh -go install github.com/graugans/go-ovp8xx/cmd/ovp8xx@latest +go install github.com/graugans/go-ovp8xx/v2/cmd/ovp8xx@latest ``` ## API usage @@ -39,7 +39,7 @@ go install github.com/graugans/go-ovp8xx/cmd/ovp8xx@latest Within in your Go project get the ovp8xx package first ``` -go get github.com/graugans/go-ovp8xx +go get github.com/graugans/go-ovp8xx/v2 ``` The following example will query the software Version of your OVP8xx. This assumes that either the OVP8xx is using the default IP address of `192.168.0.69` or the environment variable `OVP8XX_IP` is set. In case you want to set the IP in code please use `ovp8xx.NewClient(ovp8xx.WithHost("192.168.0.69"))` to construct the client. @@ -50,7 +50,7 @@ package main import ( "fmt" - "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" ) func main() { From 331406477d763d8edad4f78e73204bc57207f708 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Tue, 9 Apr 2024 08:41:03 +0200 Subject: [PATCH 06/27] ci: fix go-semantic release process Signed-off-by: Christian Ege --- .semrelrc | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .semrelrc diff --git a/.semrelrc b/.semrelrc deleted file mode 100644 index 32a2663..0000000 --- a/.semrelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "maintainedVersion": "2" -} \ No newline at end of file From a2bcd6d3f621a62a85ce87b25ed5cb5f3898efbe Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Fri, 12 Apr 2024 14:33:28 +0200 Subject: [PATCH 07/27] fix: unexpected debug output This close: #7 --- pkg/pcic/protocol.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/pcic/protocol.go b/pkg/pcic/protocol.go index 0bec80c..7ef1038 100644 --- a/pkg/pcic/protocol.go +++ b/pkg/pcic/protocol.go @@ -160,7 +160,6 @@ func errorParser(data []byte) (ErrorMessage, error) { } func asyncResultParser(data []byte) (Frame, error) { - fmt.Printf("Async Data received\n") frame := Frame{} var err error contentDecorated := data[:len(data)-delimiterFieldLength] From 4f784b50eb695b6ff7eac06da89f8cfe9fe9d33a Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 13 Apr 2024 17:46:12 +0200 Subject: [PATCH 08/27] feat: add a function to wait for the device to become ready Signed-off-by: Christian Ege --- cmd/ovp8xx/cmd/waitforonline.go | 52 +++++++++++++++++++++++++++++++++ pkg/ovp8xx/client.go | 43 +++++++++++++++++++++++++++ pkg/ovp8xx/rpc.go | 47 +++++++++++++++++++++++++---- 3 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 cmd/ovp8xx/cmd/waitforonline.go diff --git a/cmd/ovp8xx/cmd/waitforonline.go b/cmd/ovp8xx/cmd/waitforonline.go new file mode 100644 index 0000000..4c39a70 --- /dev/null +++ b/cmd/ovp8xx/cmd/waitforonline.go @@ -0,0 +1,52 @@ +/* +Copyright © 2023 Christian Ege +*/ +package cmd + +import ( + "fmt" + "time" + + "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/spf13/cobra" +) + +func waitForOnlineCommand(cmd *cobra.Command, args []string) error { + var ok = false + var err error + helper, err := NewHelper(cmd) + if err != nil { + return err + } + + o3r := ovp8xx.NewClient( + ovp8xx.WithHost(helper.hostname()), + ) + + timeout, err := cmd.Flags().GetUint("timeout") + if err != nil { + return err + } + + if ok, err = o3r.IsAvailable(time.Duration(timeout) * time.Second); err != nil { + return err + } + if ok { + fmt.Printf("The device is available now\n") + } + return nil +} + +// waitForOnlineCmd represents the wait command +var waitForOnlineCmd = &cobra.Command{ + Use: "WaitForOnline", + Short: "Wait until the device is accessible", + Long: `This command is maybe useful after a reboot or power on. +It can be used to wait until the device can handle requests`, + RunE: waitForOnlineCommand, +} + +func init() { + rootCmd.AddCommand(waitForOnlineCmd) + waitForOnlineCmd.Flags().Uint("timeout", 120, "Timeout in Seconds to wait until the device is accessible") +} diff --git a/pkg/ovp8xx/client.go b/pkg/ovp8xx/client.go index d8497f4..f6e45f6 100644 --- a/pkg/ovp8xx/client.go +++ b/pkg/ovp8xx/client.go @@ -1,7 +1,9 @@ package ovp8xx import ( + "context" "fmt" + "time" ) type ( @@ -16,6 +18,13 @@ type ( } ) +// NewClient creates a new OVP8xx client with the provided options. +// The opts parameter is a variadic parameter that allows specifying +// multiple client options. +// Example usage: +// +// client := NewClient(WithTimeout(10 * time.Second), WithRetry(3)) +// // ... func NewClient(opts ...ClientOption) *Client { // Initialise with default values client := &Client{ @@ -30,15 +39,49 @@ func NewClient(opts ...ClientOption) *Client { return client } +// WithHost sets the host for the OVP8xx client. +// It returns a ClientOption function that can be used to configure the client. func WithHost(host string) ClientOption { return func(c *Client) { c.host = host } } +// GetDiagnosticClient returns a new instance of DiagnosisClient that can be used to perform diagnostic operations on the OVP8xx device. func (device *Client) GetDiagnosticClient() *DiagnosisClient { client := &DiagnosisClient{} client.host = device.host client.url = fmt.Sprintf("http://%s/api/rpc/v1/com.ifm.diagnostic/", client.host) return client } + +// IsAvailable checks if the client is available by making a XML-RPC get request to query the "/device" object. +// This is useful to wait until a device is ready for communication. +// It returns a boolean indicating the availability status and an error if any. +// The function uses a timeout duration to limit the execution time of the request. +func (d *Client) IsAvailable(timeout time.Duration) (bool, error) { + var err error + proc := make(chan struct{}, 1) + + go func() { + for { + if _, err := d.Get([]string{"/device"}); err != nil { + // In case of an error retry, regardless of an timeout + continue + } + // we are done, the get call was successful + proc <- struct{}{} + } + }() + + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + select { + case <-ctx.Done(): + return false, fmt.Errorf("timeout occurred while checking if the device is available: %w", ctx.Err()) + case <-proc: + return true, err + } +} diff --git a/pkg/ovp8xx/rpc.go b/pkg/ovp8xx/rpc.go index 8490a5f..6fcb592 100644 --- a/pkg/ovp8xx/rpc.go +++ b/pkg/ovp8xx/rpc.go @@ -1,9 +1,26 @@ package ovp8xx import ( + "errors" + "net" + "alexejk.io/go-xmlrpc" ) +// IsTimeoutError checks if the given error is a network timeout error. +// It returns true if the error is a network error and the timeout flag is set, otherwise it returns false. +func IsTimeoutError(err error) bool { + var netErr net.Error + if errors.As(err, &netErr) && netErr.Timeout() { + return true + } + return false +} + +// Get retrieves the configuration for the specified pointers from the OVP8xx device. +// The pointers parameter is a slice of strings that contains the pointers to retrieve the configuration for. +// The function returns the retrieved configuration as a Config struct and an error if any occurred. +// Example usage: config, err := device.Get([]string{"/device", "/ports"}) func (device *Client) Get(pointers []string) (Config, error) { client, err := xmlrpc.NewClient(device.url) if err != nil { @@ -26,6 +43,8 @@ func (device *Client) Get(pointers []string) (Config, error) { return *NewConfig(WitJSONString(result.JSON)), nil } +// Set sets the configuration of the OVP8xx device. +// It takes a Config object as input and returns an error if any. func (device *Client) Set(conf Config) error { client, err := xmlrpc.NewClient(device.url) if err != nil { @@ -37,13 +56,11 @@ func (device *Client) Set(conf Config) error { Data string }{Data: conf.String()} - if err := client.Call("set", arg, nil); err != nil { - return err - } - - return nil + return client.Call("set", arg, nil) } +// GetInit retrieves the initial configuration from the OVP8xx device. +// It returns a Config struct representing the device configuration and an error if any. func (device *Client) GetInit() (Config, error) { client, err := xmlrpc.NewClient(device.url) if err != nil { @@ -62,6 +79,10 @@ func (device *Client) GetInit() (Config, error) { return *NewConfig(WitJSONString(result.JSON)), nil } +// SaveInit saves the configuration of the OVP8xx device. +// If no pointers are provided, it saves the complete configuration. +// If pointers are provided, it saves only the specified configuration pointers. +// It returns an error if there was a problem saving the configuration. func (device *Client) SaveInit(pointers []string) error { client, err := xmlrpc.NewClient(device.url) if err != nil { @@ -76,10 +97,15 @@ func (device *Client) SaveInit(pointers []string) error { arg := &struct { Pointers []string - }{Pointers: pointers} + }{ + Pointers: pointers, + } return client.Call("saveInit", arg, nil) } +// FactoryReset performs a factory reset on the OVP8xx device. +// If keepNetworkSettings is set to true, the network settings will be preserved after the reset. +// Returns an error if the factory reset fails or if there is an issue with the XML-RPC client. func (device *Client) FactoryReset(keepNetworkSettings bool) error { client, err := xmlrpc.NewClient(device.url) if err != nil { @@ -95,6 +121,9 @@ func (device *Client) FactoryReset(keepNetworkSettings bool) error { return client.Call("factoryReset", arg, nil) } +// GetSchema retrieves the schema for the specified pointers from the OVP8xx device. +// It returns the schema in JSON format as a string. +// If an error occurs during the retrieval process, it returns an empty string and the error. func (device *Client) GetSchema(pointers []string) (string, error) { client, err := xmlrpc.NewClient(device.url) if err != nil { @@ -114,6 +143,8 @@ func (device *Client) GetSchema(pointers []string) (string, error) { return result.JSON, nil } +// Reboot sends a reboot command to the OVP8xx device. +// If an error occurs during the connection or the method call, it is returned. func (device *Client) Reboot() error { client, err := xmlrpc.NewClient(device.url) if err != nil { @@ -137,6 +168,8 @@ func (device *Client) RebootToSWUpdate() error { return client.Call("rebootToRecovery", nil, nil) } +// GetFiltered retrieves a filtered configuration from the DiagnosisClient. +// It takes a Config object as input and returns a Config object and an error. func (device *DiagnosisClient) GetFiltered(conf Config) (Config, error) { client, err := xmlrpc.NewClient(device.url) if err != nil { @@ -159,6 +192,8 @@ func (device *DiagnosisClient) GetFiltered(conf Config) (Config, error) { return *NewConfig(WitJSONString(result.JSON)), nil } +// GetFilterSchema retrieves the filter schema from the DiagnosisClient. +// It returns a Config object representing the filter schema and an error if any. func (device *DiagnosisClient) GetFilterSchema() (Config, error) { client, err := xmlrpc.NewClient(device.url) if err != nil { From 0ab7a052b4481f7cee9a0cad129806052c1812f4 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 13 Apr 2024 17:59:09 +0200 Subject: [PATCH 09/27] fix: a linter warning about ineffectual assignment Signed-off-by: Christian Ege --- cmd/ovp8xx/cmd/waitforonline.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/ovp8xx/cmd/waitforonline.go b/cmd/ovp8xx/cmd/waitforonline.go index 4c39a70..45adb17 100644 --- a/cmd/ovp8xx/cmd/waitforonline.go +++ b/cmd/ovp8xx/cmd/waitforonline.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Christian Ege +Copyright © 2024 Christian Ege */ package cmd @@ -12,7 +12,7 @@ import ( ) func waitForOnlineCommand(cmd *cobra.Command, args []string) error { - var ok = false + var ok bool var err error helper, err := NewHelper(cmd) if err != nil { From 85b3949451f080d8aa57a68d0b7f0f2ae0ddb66d Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 13 Apr 2024 18:06:15 +0200 Subject: [PATCH 10/27] fix: module import path Signed-off-by: Christian Ege --- cmd/ovp8xx/cmd/waitforonline.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ovp8xx/cmd/waitforonline.go b/cmd/ovp8xx/cmd/waitforonline.go index 45adb17..61c8e7b 100644 --- a/cmd/ovp8xx/cmd/waitforonline.go +++ b/cmd/ovp8xx/cmd/waitforonline.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "github.com/graugans/go-ovp8xx/pkg/ovp8xx" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" "github.com/spf13/cobra" ) From 34ebfbc4708ab68f86208b08aebf5b04c03f642d Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 13 Apr 2024 18:08:42 +0200 Subject: [PATCH 11/27] build: update after go mod tidy Signed-off-by: Christian Ege --- go.mod | 3 +-- go.sum | 10 +--------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 61cf001..e7051ea 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( alexejk.io/go-xmlrpc v0.4.0 github.com/spf13/cobra v1.7.0 + github.com/stretchr/testify v1.8.4 ) require ( @@ -12,7 +13,5 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 95a778e..d11e499 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,6 @@ alexejk.io/go-xmlrpc v0.4.0 h1:HvaeCuACuF2NBJruG90AJKc5JHRGj9vKxu2ltJntQR4= alexejk.io/go-xmlrpc v0.4.0/go.mod h1:M7f2OzqvZIWrN1LftR4uwW/bLpxrFoQYjWfm4gQB4JA= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -13,16 +12,9 @@ github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 65b2261f22cddc8626563dfe8b5a9131d8ed00b4 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 13 Apr 2024 18:51:20 +0200 Subject: [PATCH 12/27] build: install git-lfs in the dev container --- .devcontainer/postCreateCommand.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh index 97ca951..9fa2e0b 100755 --- a/.devcontainer/postCreateCommand.sh +++ b/.devcontainer/postCreateCommand.sh @@ -1,4 +1,22 @@ #!/bin/bash +# install missing packages +sudo apt-get update && + sudo apt-get install git-lfs \ + python3-pip \ + python3-venv \ + shfmt \ + shellcheck && + sudo apt-get clean && + sudo rm -rf /var/lib/apt/lists/* + +# Fetch git large files +git lfs fetch --all + +# Install Python packages +export PATH=~/.local/bin:$PATH +pip3 install --break-system-packages --upgrade pip +pip3 install --break-system-packages -r requirements.txt + # Go CI Lint -curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.54.0 +curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)/bin" v1.54.0 From 0958ad982913ff24ab69f330cf60e3f069068498 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 13 Apr 2024 18:59:45 +0200 Subject: [PATCH 13/27] fix: typos in code comments and command descriptions --- cmd/ovp8xx/cmd/factoryReset.go | 2 +- cmd/ovp8xx/cmd/get.go | 4 ++-- cmd/ovp8xx/cmd/getSchema.go | 4 ++-- cmd/ovp8xx/cmd/getinit.go | 2 +- cmd/ovp8xx/cmd/pcic.go | 2 +- cmd/ovp8xx/cmd/saveinit.go | 6 +++--- cmd/ovp8xx/cmd/waitforonline.go | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmd/ovp8xx/cmd/factoryReset.go b/cmd/ovp8xx/cmd/factoryReset.go index e773fb5..189db54 100644 --- a/cmd/ovp8xx/cmd/factoryReset.go +++ b/cmd/ovp8xx/cmd/factoryReset.go @@ -14,7 +14,7 @@ var factoryResetCmd = &cobra.Command{ Short: "Performs a factory reset of the device", Long: `Sometime one wants a fresh start. -The command factoryReset resets all settings to their defaults and erases any addtional data like Docker containers.`, +The command factoryReset resets all settings to their defaults and erases any additional data like Docker containers.`, RunE: func(cmd *cobra.Command, args []string) error { keepNetworkSettings, err := cmd.Flags().GetBool("keepnetworksettings") if err != nil { diff --git a/cmd/ovp8xx/cmd/get.go b/cmd/ovp8xx/cmd/get.go index fc4d7cf..6cb2728 100644 --- a/cmd/ovp8xx/cmd/get.go +++ b/cmd/ovp8xx/cmd/get.go @@ -40,11 +40,11 @@ Valid queries are for example: - To query all ports including all sub elements the query "/ports" can be used. In contrast to the concept of a JSON pointer the OVP8xx does not response with the data -the pointer is pointing to, it returns the full object hirachie with the encapsulating +the pointer is pointing to, it returns the full object hierarchy with the encapsulating object paths. A query of the name of the "port6" (/ports/port6/info/name) not just returns the object of that port, -it also keeps the hirachy intact: +it also keeps the hierarchy intact: { "ports": diff --git a/cmd/ovp8xx/cmd/getSchema.go b/cmd/ovp8xx/cmd/getSchema.go index 0baa643..f19da9a 100644 --- a/cmd/ovp8xx/cmd/getSchema.go +++ b/cmd/ovp8xx/cmd/getSchema.go @@ -34,7 +34,7 @@ var getSchemaCmd = &cobra.Command{ Use: "getSchema", Short: "Retrieve the currently used JSON schema from the device", Long: `The OVP8xx getSchema command accepts a list of JSON pointers. -The JSON schema provides details about multiple aspects of a paramter. It +The JSON schema provides details about multiple aspects of a parameter. It contains information like the type of a parameter and its defaults. It also provides information weather a parameter is readOnly or not. @@ -76,7 +76,7 @@ The pointer '/device/swVersion/diagnostics' for example provides this informatio "type": "object" } -When no query is provided the complete schema is returend. +When no query is provided the complete schema is returned. `, RunE: getSchemaCommand, } diff --git a/cmd/ovp8xx/cmd/getinit.go b/cmd/ovp8xx/cmd/getinit.go index 08b7238..1a11477 100644 --- a/cmd/ovp8xx/cmd/getinit.go +++ b/cmd/ovp8xx/cmd/getinit.go @@ -36,7 +36,7 @@ var getInitCmd = &cobra.Command{ Long: `The OVP8xx provides a way to store a configuration on the device NOTE: This shall be used with care, because it may lead to an system which is no -longer useable when the expectation from the safed configuration is no longer met.`, +longer useable when the expectation from the saved configuration is no longer met.`, RunE: getInitCommand, } diff --git a/cmd/ovp8xx/cmd/pcic.go b/cmd/ovp8xx/cmd/pcic.go index e427a31..5c0b6d7 100644 --- a/cmd/ovp8xx/cmd/pcic.go +++ b/cmd/ovp8xx/cmd/pcic.go @@ -22,7 +22,7 @@ type PCICReceiver struct { // It takes a pcic.Frame as a parameter. func (r *PCICReceiver) Result(frame pcic.Frame) { r.frame = frame - fmt.Printf("Framecount: %d\n", r.framecount) + fmt.Printf("Frame count: %d\n", r.framecount) r.framecount++ } diff --git a/cmd/ovp8xx/cmd/saveinit.go b/cmd/ovp8xx/cmd/saveinit.go index d46667d..4f0366d 100644 --- a/cmd/ovp8xx/cmd/saveinit.go +++ b/cmd/ovp8xx/cmd/saveinit.go @@ -12,11 +12,11 @@ import ( var saveInitCmd = &cobra.Command{ Use: "saveInit", Short: "Saves the init configuration on the device", - Long: `To store the configuration persistant on the device the command saveInit can be used. + Long: `To store the configuration persistent on the device the command saveInit can be used. -A safed configuration persists a reboot. This is best used in combination with the "set" command. +A saved configuration persists a reboot. This is best used in combination with the "set" command. -Please use this with care. The scope should be as narrow as posible, to prevent any conflicts. +Please use this with care. The scope should be as narrow as possible, to prevent any conflicts. In case no JSON Pointer is provided the complete configuration is saved`, RunE: func(cmd *cobra.Command, args []string) error { pointers, err := cmd.Flags().GetStringSlice("pointer") diff --git a/cmd/ovp8xx/cmd/waitforonline.go b/cmd/ovp8xx/cmd/waitforonline.go index 61c8e7b..7fb832e 100644 --- a/cmd/ovp8xx/cmd/waitforonline.go +++ b/cmd/ovp8xx/cmd/waitforonline.go @@ -41,7 +41,7 @@ func waitForOnlineCommand(cmd *cobra.Command, args []string) error { var waitForOnlineCmd = &cobra.Command{ Use: "WaitForOnline", Short: "Wait until the device is accessible", - Long: `This command is maybe useful after a reboot or power on. + Long: `This command is maybe useful after a reboot or power on. It can be used to wait until the device can handle requests`, RunE: waitForOnlineCommand, } From dc56adca5b4c70a476f345976d75e763fcd128b9 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 13 Apr 2024 19:00:41 +0200 Subject: [PATCH 14/27] build: add goimports to postCreateCommand.sh --- .devcontainer/postCreateCommand.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh index 9fa2e0b..8fa473d 100755 --- a/.devcontainer/postCreateCommand.sh +++ b/.devcontainer/postCreateCommand.sh @@ -20,3 +20,6 @@ pip3 install --break-system-packages -r requirements.txt # Go CI Lint curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)/bin" v1.54.0 + +# Go tools +go install golang.org/x/tools/cmd/goimports@latest From 9ae46522bf4fe0598766a16b6de426f7d9f4466d Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 13 Apr 2024 19:22:22 +0200 Subject: [PATCH 15/27] feature: add diagnostic configuration stages to device availability check --- pkg/ovp8xx/client.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/pkg/ovp8xx/client.go b/pkg/ovp8xx/client.go index f6e45f6..b8e7aa0 100644 --- a/pkg/ovp8xx/client.go +++ b/pkg/ovp8xx/client.go @@ -2,7 +2,9 @@ package ovp8xx import ( "context" + "encoding/json" "fmt" + "slices" "time" ) @@ -63,14 +65,30 @@ func (d *Client) IsAvailable(timeout time.Duration) (bool, error) { var err error proc := make(chan struct{}, 1) + result := struct { + Device struct { + Diagnostic struct { + ConfInitStages []string `json:"confInitStages"` + } `json:"diagnostic"` + } `json:"device"` + }{} + conf := *NewConfig() go func() { for { - if _, err := d.Get([]string{"/device"}); err != nil { + if conf, err = d.Get([]string{"/device/diagnostic/confInitStages"}); err != nil { // In case of an error retry, regardless of an timeout continue } - // we are done, the get call was successful - proc <- struct{}{} + // Unmarshal the data into the result struct + if err = json.Unmarshal([]byte(conf.String()), &result); err != nil { + // In case of an error retry until the timeout + continue + } + // Check if the device is ready + if slices.Contains(result.Device.Diagnostic.ConfInitStages, "device") { + // we are done, the get call was successful + proc <- struct{}{} + } } }() From 6769c1186ed0b8fd36021043eff8fd2710aa03f9 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 13 Apr 2024 19:30:32 +0200 Subject: [PATCH 16/27] doc: add comments about the used struct --- pkg/ovp8xx/client.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/ovp8xx/client.go b/pkg/ovp8xx/client.go index b8e7aa0..949432f 100644 --- a/pkg/ovp8xx/client.go +++ b/pkg/ovp8xx/client.go @@ -64,7 +64,9 @@ func (device *Client) GetDiagnosticClient() *DiagnosisClient { func (d *Client) IsAvailable(timeout time.Duration) (bool, error) { var err error proc := make(chan struct{}, 1) - + conf := *NewConfig() + // result is a struct that represents the response from the OVP8xx device. + // It contains information about the device's diagnostic data, such as the configuration initialization stages. result := struct { Device struct { Diagnostic struct { @@ -72,7 +74,7 @@ func (d *Client) IsAvailable(timeout time.Duration) (bool, error) { } `json:"diagnostic"` } `json:"device"` }{} - conf := *NewConfig() + go func() { for { if conf, err = d.Get([]string{"/device/diagnostic/confInitStages"}); err != nil { From 984d0490e570ca832db6f998d56c8c26617bea58 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sun, 14 Apr 2024 10:54:06 +0200 Subject: [PATCH 17/27] refactor: IsAvailable function to support configurable waiting stages --- cmd/ovp8xx/cmd/waitforonline.go | 6 ++++- pkg/ovp8xx/client.go | 41 +++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/cmd/ovp8xx/cmd/waitforonline.go b/cmd/ovp8xx/cmd/waitforonline.go index 7fb832e..7a9d0f9 100644 --- a/cmd/ovp8xx/cmd/waitforonline.go +++ b/cmd/ovp8xx/cmd/waitforonline.go @@ -28,7 +28,11 @@ func waitForOnlineCommand(cmd *cobra.Command, args []string) error { return err } - if ok, err = o3r.IsAvailable(time.Duration(timeout) * time.Second); err != nil { + timeOutDuration := time.Duration(timeout) * time.Second + if ok, err = o3r.IsAvailable( + timeOutDuration, + ovp8xx.AndPortsAreOnline(), + ); err != nil { return err } if ok { diff --git a/pkg/ovp8xx/client.go b/pkg/ovp8xx/client.go index 949432f..79696a7 100644 --- a/pkg/ovp8xx/client.go +++ b/pkg/ovp8xx/client.go @@ -57,14 +57,51 @@ func (device *Client) GetDiagnosticClient() *DiagnosisClient { return client } +// WaitForConfig represents the configuration for waiting for a specific stage. +type WaitForConfig struct { + stage string // The stage to wait for. +} + +// WaitForOption is a function type that is used as an option for configuring the behavior of the WaitFor function. +// It takes a pointer to a WaitForConfig struct as a parameter and can be used to modify its properties. +type WaitForOption func(c *WaitForConfig) + +// AndPortsAreOnline returns a WaitForOption function that sets the stage to "ports". +func AndPortsAreOnline() WaitForOption { + return func(w *WaitForConfig) { + w.stage = "ports" + } +} + +// AndAppsAreOnline returns a WaitForOption function that sets the stage to "applications". +func AndAppsAreOnline() WaitForOption { + return func(w *WaitForConfig) { + w.stage = "applications" + } +} + // IsAvailable checks if the client is available by making a XML-RPC get request to query the "/device" object. // This is useful to wait until a device is ready for communication. // It returns a boolean indicating the availability status and an error if any. // The function uses a timeout duration to limit the execution time of the request. -func (d *Client) IsAvailable(timeout time.Duration) (bool, error) { +// Example usage: +// +// ok, err := device.IsAvailable(time.Duration(timeout) * time.Second, ovp8xx.AndPortsAreOnline()) +// // ... +func (d *Client) IsAvailable(timeout time.Duration, opts ...WaitForOption) (bool, error) { var err error proc := make(chan struct{}, 1) conf := *NewConfig() + + waitingFor := &WaitForConfig{ + stage: "device", + } + + // Apply wait options + for _, opt := range opts { + opt(waitingFor) + } + // result is a struct that represents the response from the OVP8xx device. // It contains information about the device's diagnostic data, such as the configuration initialization stages. result := struct { @@ -87,7 +124,7 @@ func (d *Client) IsAvailable(timeout time.Duration) (bool, error) { continue } // Check if the device is ready - if slices.Contains(result.Device.Diagnostic.ConfInitStages, "device") { + if slices.Contains(result.Device.Diagnostic.ConfInitStages, waitingFor.stage) { // we are done, the get call was successful proc <- struct{}{} } From e8637433b9f1ec406941ce0dd4e6c879a06a086e Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sun, 14 Apr 2024 10:54:46 +0200 Subject: [PATCH 18/27] doc: update client and rpc usage examples --- pkg/ovp8xx/client.go | 2 +- pkg/ovp8xx/rpc.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/ovp8xx/client.go b/pkg/ovp8xx/client.go index 79696a7..1854674 100644 --- a/pkg/ovp8xx/client.go +++ b/pkg/ovp8xx/client.go @@ -25,7 +25,7 @@ type ( // multiple client options. // Example usage: // -// client := NewClient(WithTimeout(10 * time.Second), WithRetry(3)) +// client := NewClient(WithHost("192.168.47.11")) // // ... func NewClient(opts ...ClientOption) *Client { // Initialise with default values diff --git a/pkg/ovp8xx/rpc.go b/pkg/ovp8xx/rpc.go index 6fcb592..e4604ef 100644 --- a/pkg/ovp8xx/rpc.go +++ b/pkg/ovp8xx/rpc.go @@ -20,7 +20,10 @@ func IsTimeoutError(err error) bool { // Get retrieves the configuration for the specified pointers from the OVP8xx device. // The pointers parameter is a slice of strings that contains the pointers to retrieve the configuration for. // The function returns the retrieved configuration as a Config struct and an error if any occurred. -// Example usage: config, err := device.Get([]string{"/device", "/ports"}) +// Example usage: +// +// config, err := device.Get([]string{"/device", "/ports"}) +// // ... func (device *Client) Get(pointers []string) (Config, error) { client, err := xmlrpc.NewClient(device.url) if err != nil { From 7ad3758ffeff3ef23081ffc272fa1429bb5d8f34 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sun, 14 Apr 2024 11:00:14 +0200 Subject: [PATCH 19/27] doc: Add Go Reference badge to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 798be81..c34b856 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ A GO module and cli to access the ifm OVP8xx series of devices. [![GitHub go.mod Go version of a Go module](https://img.shields.io/github/go-mod/go-version/gomods/athens.svg)](https://github.com/gomods/athens) +[![Go Reference](https://pkg.go.dev/badge/github.com/graugans/go-ovp8xx/v2.svg)](https://pkg.go.dev/github.com/graugans/go-ovp8xx/v2) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![example workflow](https://github.com/graugans/go-ovp8xx/actions/workflows/go.yml/badge.svg) [![codecov](https://codecov.io/gh/graugans/go-ovp8xx/graph/badge.svg?token=BU6UPYCUPI)](https://codecov.io/gh/graugans/go-ovp8xx) From c5b13692542add7f0d691c2eceadd96b05bbb521 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sun, 14 Apr 2024 11:05:27 +0200 Subject: [PATCH 20/27] doc: Update Go module version badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c34b856..e2e6460 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A GO module and cli to access the ifm OVP8xx series of devices. -[![GitHub go.mod Go version of a Go module](https://img.shields.io/github/go-mod/go-version/gomods/athens.svg)](https://github.com/gomods/athens) +[![GitHub go.mod Go version of a Go module](https://img.shields.io/github/go-mod/go-version/graugans/go-ovp8xx.svg)](https://github.com/graugans/go-ovp8xx) [![Go Reference](https://pkg.go.dev/badge/github.com/graugans/go-ovp8xx/v2.svg)](https://pkg.go.dev/github.com/graugans/go-ovp8xx/v2) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![example workflow](https://github.com/graugans/go-ovp8xx/actions/workflows/go.yml/badge.svg) From 91285cc0c1ec3cf6572b815a3c8fe559d0587277 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sun, 14 Apr 2024 11:26:49 +0200 Subject: [PATCH 21/27] fix: Update package installation command in postCreateCommand.sh Otherwise the user gets a prompt to answer Yes in the terminal. May block forever --- .devcontainer/postCreateCommand.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh index 8fa473d..4183b47 100755 --- a/.devcontainer/postCreateCommand.sh +++ b/.devcontainer/postCreateCommand.sh @@ -2,7 +2,7 @@ # install missing packages sudo apt-get update && - sudo apt-get install git-lfs \ + sudo apt-get install -q -y git-lfs \ python3-pip \ python3-venv \ shfmt \ From 141c0c04b73e42f22f1d80173466218a4d73be1b Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sun, 14 Apr 2024 11:26:49 +0200 Subject: [PATCH 22/27] fix: Update package installation command in postCreateCommand.sh Otherwise the user gets a prompt to answer Yes in the terminal. May block forever --- .devcontainer/postCreateCommand.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh index 8fa473d..4183b47 100755 --- a/.devcontainer/postCreateCommand.sh +++ b/.devcontainer/postCreateCommand.sh @@ -2,7 +2,7 @@ # install missing packages sudo apt-get update && - sudo apt-get install git-lfs \ + sudo apt-get install -q -y git-lfs \ python3-pip \ python3-venv \ shfmt \ From ca35c8e4651dbdde0948a637804e283f0b25a533 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Wed, 8 May 2024 15:22:02 +0200 Subject: [PATCH 23/27] feature: add support for filtering the output of get This is done with the help of the text/template package. One example to change the state of all ports except port6 to state CONF: {{ $state := "CONF" }}{ "ports": { {{$p := prefix ", "}}{{- range $key, $val := .ports -}}{{ if ne $key "port6" }}{{call $p}}"{{- $key -}}":{"state": "{{ $state }}"}{{end}}{{- end }} } } There are two custom functions toJSON and prefix --- cmd/ovp8xx/cmd/get.go | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/cmd/ovp8xx/cmd/get.go b/cmd/ovp8xx/cmd/get.go index 6cb2728..4965cd0 100644 --- a/cmd/ovp8xx/cmd/get.go +++ b/cmd/ovp8xx/cmd/get.go @@ -4,10 +4,39 @@ Copyright © 2023 Christian Ege package cmd import ( + "encoding/json" + "fmt" + "os" + "text/template" + "github.com/graugans/go-ovp8xx/v2/pkg/ovp8xx" "github.com/spf13/cobra" ) +// toJSON converts the given object to a JSON string representation. +// If an error occurs during marshaling, an empty string is returned. +// This is taken from https://github.com/intel/tfortools +func toJSON(obj interface{}) string { + b, err := json.MarshalIndent(obj, "", "\t") + if err != nil { + return "" + } + return string(b) +} + +// prefix can be used to create a list separated by s and the very first +// element is not prefixed. +func prefix(s string) func() string { + i := -1 + return func() string { + i++ + if i == 0 { + return "" + } + return s + } +} + func getCommand(cmd *cobra.Command, args []string) error { var result ovp8xx.Config var err error @@ -23,6 +52,31 @@ func getCommand(cmd *cobra.Command, args []string) error { if result, err = o3r.Get(helper.jsonPointers()); err != nil { return err } + + if cmd.Flags().Changed("format") { + format, err := cmd.Flags().GetString("format") + if err != nil { + return fmt.Errorf("unable to get the format string from the command line: %w", err) + } + var inputData interface{} + if err = json.Unmarshal([]byte(result.String()), &inputData); err != nil { + return fmt.Errorf("unable to unmarshal the JSON data from the 'get' call: %w", err) + } + templateFunctions := template.FuncMap{ + "toJSON": toJSON, + "prefix": prefix, + } + tmpl, err := template.New("output").Funcs(templateFunctions).Parse(format) + if err != nil { + return fmt.Errorf("unable to parse the template: %w", err) + } + + if err := tmpl.Execute(os.Stdout, inputData); err != nil { + return fmt.Errorf("unable to execute the template: %w", err) + } + return nil + } + if err := helper.printJSONResult(result.String()); err != nil { return err } @@ -66,4 +120,5 @@ func init() { rootCmd.AddCommand(getCmd) getCmd.Flags().StringSliceP("pointer", "p", []string{""}, "A JSON pointer to be queried") getCmd.Flags().Bool("pretty", false, "Pretty print the JSON received from the device") + getCmd.Flags().String("format", "", "Specify an alternative format for the JSON output") } From b35da82d3c807cefc560322a1cd4a97238cf7835 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Tue, 14 May 2024 14:56:05 +0200 Subject: [PATCH 24/27] fix: use the right version information when go install is used Signed-off-by: Christian Ege --- cmd/ovp8xx/cmd/root.go | 3 +++ cmd/ovp8xx/ovp8xx.go | 10 +++++++ internal/versioninfo/version.go | 46 +++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 internal/versioninfo/version.go diff --git a/cmd/ovp8xx/cmd/root.go b/cmd/ovp8xx/cmd/root.go index dce12e1..b1b5435 100644 --- a/cmd/ovp8xx/cmd/root.go +++ b/cmd/ovp8xx/cmd/root.go @@ -11,6 +11,8 @@ import ( "github.com/spf13/cobra" ) +// SetVersionInfo sets the version information for the root command. +// It formats the version, commit, and date into a string and assigns it to the rootCmd.Version variable. func SetVersionInfo(version, commit, date string) { rootCmd.Version = fmt.Sprintf("%s (Built on %s from Git SHA %s)", version, date, commit) } @@ -33,4 +35,5 @@ func Execute() { func init() { rootCmd.PersistentFlags().String("ip", ovp8xx.GetEnv("OVP8XX_IP", "192.168.0.69"), "The IP address or hostname of the OVP8XX. If not provided the default will be taken from the environment variable OVP8XX_IP") + } diff --git a/cmd/ovp8xx/ovp8xx.go b/cmd/ovp8xx/ovp8xx.go index 89e8b00..e7a34f1 100644 --- a/cmd/ovp8xx/ovp8xx.go +++ b/cmd/ovp8xx/ovp8xx.go @@ -2,6 +2,7 @@ package main import ( "github.com/graugans/go-ovp8xx/v2/cmd/ovp8xx/cmd" + "github.com/graugans/go-ovp8xx/v2/internal/versioninfo" ) var ( @@ -11,6 +12,15 @@ var ( ) func main() { + // If the version is "dev", it means that the binary is built using "go install", + // "go build" or "go run". + // However, if the binary is build by Goreleaser we use that version. + if version == "dev" { + version = versioninfo.Version + commit = versioninfo.Revision + date = versioninfo.LastCommit.String() + } + cmd.SetVersionInfo( version, commit, diff --git a/internal/versioninfo/version.go b/internal/versioninfo/version.go new file mode 100644 index 0000000..63335e9 --- /dev/null +++ b/internal/versioninfo/version.go @@ -0,0 +1,46 @@ +// Package versioninfo uses runtime.ReadBuildInfo() to set global executable revision information if possible. +package versioninfo + +// SPDX-License-Identifier: MIT +// Copyright (c) 2021 Carl Johnson +// Based on https://github.com/earthboundkid/versioninfo + +import ( + "runtime/debug" + "time" +) + +var ( + // Version will be the version tag if the binary is built with "go install url/tool@version". + // If the binary is built some other way, it will be "(devel)". + Version = "unknown" + // Revision is taken from the vcs.revision tag in Go 1.18+. + Revision = "unknown" + // LastCommit is taken from the vcs.time tag in Go 1.18+. + LastCommit time.Time + // DirtyBuild is taken from the vcs.modified tag in Go 1.18+. + DirtyBuild = true +) + +func init() { + info, ok := debug.ReadBuildInfo() + if !ok { + return + } + for _, kv := range info.Settings { + if kv.Value == "" { + continue + } + switch kv.Key { + case "vcs.revision": + Revision = kv.Value + case "vcs.time": + LastCommit, _ = time.Parse(time.RFC3339, kv.Value) + case "vcs.modified": + DirtyBuild = kv.Value == "true" + } + } + if info.Main.Version != "" { + Version = info.Main.Version + } +} From 0b8c5f80a24f387c7f75034928d0d7bb1423ef6c Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Tue, 14 May 2024 16:17:37 +0200 Subject: [PATCH 25/27] fix(get): avoid usage of --format and --pretty at the same time --- cmd/ovp8xx/cmd/get.go | 5 +++++ cmd/ovp8xx/cmd/helper.go | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cmd/ovp8xx/cmd/get.go b/cmd/ovp8xx/cmd/get.go index 4965cd0..cd9d19a 100644 --- a/cmd/ovp8xx/cmd/get.go +++ b/cmd/ovp8xx/cmd/get.go @@ -5,6 +5,7 @@ package cmd import ( "encoding/json" + "errors" "fmt" "os" "text/template" @@ -54,6 +55,10 @@ func getCommand(cmd *cobra.Command, args []string) error { } if cmd.Flags().Changed("format") { + if helper.prettyPrint() { + return errors.New("you can't use --pretty and --format at the same time") + } + format, err := cmd.Flags().GetString("format") if err != nil { return fmt.Errorf("unable to get the format string from the command line: %w", err) diff --git a/cmd/ovp8xx/cmd/helper.go b/cmd/ovp8xx/cmd/helper.go index 5376563..049df35 100644 --- a/cmd/ovp8xx/cmd/helper.go +++ b/cmd/ovp8xx/cmd/helper.go @@ -17,7 +17,7 @@ type helperConfig struct { func (c *helperConfig) printJSONResult(data string) error { var message string = data - if c.pretty { + if c.prettyPrint() { var js json.RawMessage if err := json.Unmarshal([]byte(data), &js); err != nil { return errors.New("malformed json") @@ -44,6 +44,11 @@ func (c *helperConfig) remotePort() uint16 { return c.port } +// prettyPrint returns a boolean value indicating whether the output should be pretty-printed. +func (c *helperConfig) prettyPrint() bool { + return c.pretty +} + func NewHelper(cmd *cobra.Command) (helperConfig, error) { var conf = helperConfig{} var err error From a61a88adda097c94422840cd9d0696289f71efaa Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Wed, 15 May 2024 07:51:41 +0200 Subject: [PATCH 26/27] doc(README): add a documentation about the filter --- README.md | 14 +++++--- doc/filter.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 doc/filter.md diff --git a/README.md b/README.md index e2e6460..c21a8ab 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,21 @@ A GO module and cli to access the ifm OVP8xx series of devices. This project is still a work in progress and will suffer from breaking API changes. Please be warned. In case you have any suggestions or want to contribute please feel free to open an issue or pull request. -## CLI Installation +## CLI -### Pre Build Binaries +One of the benefits of the Go language is the easy way of producing statically linked binaries to be used on all major platforms. One of the `ovp8xx` core features is the CLI interface. The design tries to stay as close as possible to the XML-RPC API. + +With the CLI you can get the configuration from the device and [filter](doc/filter.md) it as you need. After transforming the config it can be written back to the device. + +### CLI Installation + +#### Pre Build Binaries The recommended and easiest way is to download the pre-build binary from the [GitHub Release page](https://github.com/graugans/go-ovp8xx/releases). ⚠️ The Windows binary maybe flagged by a Virus Scanner, please also read the note from the [Go Team](https://go.dev/doc/faq#virus) ⚠️ -### Go get +#### Go get If you have a decent Go version installed @@ -35,7 +41,7 @@ If you have a decent Go version installed go install github.com/graugans/go-ovp8xx/v2/cmd/ovp8xx@latest ``` -## API usage +### API usage Within in your Go project get the ovp8xx package first diff --git a/doc/filter.md b/doc/filter.md new file mode 100644 index 0000000..738559e --- /dev/null +++ b/doc/filter.md @@ -0,0 +1,92 @@ +# Using Go text/template for Advanced Data Filtering + +This document demonstrates the usage of the Go text/template package to filter and format the output of the `ovp8xx get` command for manipulating the result data. There ar two custom functions created to enhance the filtering: `prefix` and `toJSON`. + +## The `prefix` Function + +The `prefix` function is designed to solve the problem of trailing commas, which are for example not allowed in JSON. The idea is to prefix each line with a comma, except for the very first one. This function is particularly useful when generating JSON output dynamically, where the number of elements may vary. + +Here's how it's used in the command: + +```sh +ovp8xx get --format '{ "ports": { {{$p := prefix ", "}}{{- range $key, $val := .ports -}}{{call $p}}"{{- $key -}}":{"state": "{{ .state }}"}{{- end }} } }' +``` + +In this command, `{{$p := prefix ", "}}` initializes the prefix function with a comma and a space. Then, `{{call $p}}` is used to insert the prefix before each port entry. The prefix function ensures that no comma is inserted before the first entry. + +The result is a JSON object where each line, except the first, is prefixed with a comma: + +```sh +{ + "ports": { + "port0": { + "state": "RUN" + }, + "port1": { + "state": "RUN" + }, + "port2": { + "state": "RUN" + }, + "port3": { + "state": "RUN" + }, + "port6": { + "state": "RUN" + } + } +} +``` + +## Print all ports with some details + +The following command retrieves all port data and formats it for easy reading: + +```sh +ovp8xx get --format '{{$p := prefix "\n"}}{{ range $port, $details := .ports }}{{call $p}}[{{ $port }}] state: {{ $details.state }},{{print "\t"}}type: {{ $details.info.features.type }},{{print "\t"}}PCIC Port: {{ $details.data.pcicTCPPort }}{{ end }}' +``` + +The result will look like: + +``` +[port0] state: RUN, type: 3D, PCIC Port: 50010 +[port1] state: RUN, type: 3D, PCIC Port: 50011 +[port2] state: RUN, type: 2D, PCIC Port: 50012 +[port3] state: RUN, type: 2D, PCIC Port: 50013 +[port6] state: RUN, type: IMU, PCIC Port: 50016 +``` + +## Modify the state of the ports + +Now we are taking the example from the [`prefix`](#the-prefix-function) function and enhance it to also modify the state of all ports (except port6) to CONF with the following command: + +```sh +ovp8xx get --format '{{ $state := "CONF" }}{ "ports": { {{$p := prefix ", "}}{{- range $key, $val := .ports -}}{{ if ne $key "port6" }}{{call $p}}"{{- $key -}}":{"state": "{{ $state }}"}{{end}}{{- end }} } }' +``` + +The output will be a JSON object that can be directly piped into the set command: + +```sh +{ + "ports": { + "port0": { + "state": "CONF" + }, + "port1": { + "state": "CONF" + }, + "port2": { + "state": "CONF" + }, + "port3": { + "state": "CONF" + } + } +} +``` + +To directly apply those changes to the device, use the following command: + +```sh +ovp8xx get --format '{{ $state := "CONF" }}{ "ports": { {{$p := prefix ", "}}{{- range $key, $val := .ports -}}{{ if ne $key "port6" }}{{call $p}}"{{- $key -}}":{"state": "{{ $state }}"}{{end}}{{- end }} } }' | ovp8xx set +``` From 3c2d4d8f3b1af023e27695ff39cdca93c9f718ff Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Wed, 15 May 2024 07:57:10 +0200 Subject: [PATCH 27/27] doc(filter): fix the spelling --- doc/filter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/filter.md b/doc/filter.md index 738559e..f67a1d9 100644 --- a/doc/filter.md +++ b/doc/filter.md @@ -1,10 +1,10 @@ # Using Go text/template for Advanced Data Filtering -This document demonstrates the usage of the Go text/template package to filter and format the output of the `ovp8xx get` command for manipulating the result data. There ar two custom functions created to enhance the filtering: `prefix` and `toJSON`. +This document demonstrates the usage of the Go text/template package to filter and format the output of the `ovp8xx get` command for manipulating the result data. There are two custom functions created to enhance the filtering: `prefix` and `toJSON`. ## The `prefix` Function -The `prefix` function is designed to solve the problem of trailing commas, which are for example not allowed in JSON. The idea is to prefix each line with a comma, except for the very first one. This function is particularly useful when generating JSON output dynamically, where the number of elements may vary. +The `prefix` function is designed to solve the problem of trailing commas, which are, for example, not allowed in JSON. The idea is to prefix each line with a comma, except for the very first one. This function is particularly useful when generating JSON output dynamically, where the number of elements may vary. Here's how it's used in the command: