From aef7d9329141c638ca2496cf621037861d166c31 Mon Sep 17 00:00:00 2001 From: Felix Gateru Date: Wed, 8 May 2024 17:34:14 +0300 Subject: [PATCH] NOISSUE - Add Continous Integration (#9) * feat: Add CI Signed-off-by: 1998-felix * ci: Add linter, dependabot and ci config Signed-off-by: 1998-felix * ci: refactor config for linter settings Signed-off-by: 1998-felix * fix: fix failing linter Signed-off-by: 1998-felix * refactor: clean up variable names Signed-off-by: 1998-felix * refactor: refactor error definition to inline Signed-off-by: 1998-felix * refactor: refactor parse code, remove inline errors Signed-off-by: 1998-felix * refactor: refactor parseCode for error handling Signed-off-by: 1998-felix --------- Signed-off-by: 1998-felix --- .github/dependabot.yml | 14 +++++ .github/workflows/ci.yml | 36 +++++++++++ .golangci.yml | 75 +++++++++++++++++++++++ cmd/main.go | 129 ++++++++++++++++++++++++--------------- coap/client.go | 9 ++- 5 files changed, 213 insertions(+), 50 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .golangci.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..98e103a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: ".github/workflows" + schedule: + interval: "monthly" + + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7fed735 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +name: Continuous Integration + +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + lint-and-build: + name: Lint and Build + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: 1.22.x + cache-dependency-path: "go.sum" + + - name: Check linting + uses: golangci/golangci-lint-action@v4 + with: + version: v1.56.2 + + - name: Build Binaries + run: | + make all -j $(nproc) diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..c6c8f3b --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,75 @@ +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +run: + concurrency: 4 + timeout: 1m + +linters-settings: + importas: + no-unaliased: true + no-extra-aliases: false + gocritic: + enabled-checks: + - importShadow + - httpNoBody + - paramTypeCombine + - emptyStringTest + - builtinShadow + - exposedSyncMutex + disabled-checks: + - appendAssign + enabled-tags: + - diagnostic + disabled-tags: + - performance + - style + - experimental + - opinionated + stylecheck: + checks: ["-ST1000", "-ST1003", "-ST1020", "-ST1021", "-ST1022"] + goheader: + template: |- + Copyright (c) Abstract Machines + SPDX-License-Identifier: Apache-2.0 + +linters: + disable-all: true + fast: true + enable: + - gocritic + - gosimple + - errcheck + - govet + - unused + - goconst + - godot + - godox + - ineffassign + - misspell + - stylecheck + - whitespace + - gci + - gofmt + - goimports + - loggercheck + - goheader + - asasalint + - asciicheck + - bidichk + - contextcheck + - decorder + - dogsled + - errchkjson + - errname + - execinquery + - exportloopref + - ginkgolinter + - gocheckcompilerdirectives + - gofumpt + - goprintffuncname + - importas + - makezero + - mirror + - nakedret + - dupword diff --git a/cmd/main.go b/cmd/main.go index 0b1f25e..a5813bd 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,3 +1,6 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + package main import ( @@ -18,13 +21,6 @@ import ( "github.com/plgd-dev/go-coap/v2/udp/message/pool" ) -const ( - get = "GET" - put = "PUT" - post = "POST" - delete = "DELETE" -) - const ( helpCmd = `Use "coap-cli --help" for help.` helpMsg = ` @@ -44,18 +40,38 @@ coap-cli post channels/0bb5ba61-a66e-4972-bab6-26f19962678f/messages/subtopic -a ` ) +var ( + errCreateClient = errors.New("failed to create client") + errSendMessage = errors.New("failed to send message") + errInvalidObsOpt = errors.New("invalid observe option") + errFailedObserve = errors.New("failed to observe resource") + errTerminatedObs = errors.New("observation terminated") + errCancelObs = errors.New("failed to cancel observation") + errCodeNotSupported = errors.New("message can be GET, POST, PUT or DELETE") +) + +type request struct { + code codes.Code + path string + host *string + port *string + cf *int + data *string + auth *string + obs *bool +} + func parseCode(code string) (codes.Code, error) { - switch code { - case get: - return codes.GET, nil - case put: - return codes.PUT, nil - case post: - return codes.POST, nil - case delete: - return codes.DELETE, nil - } - return 0, errors.New("Message can be GET, POST, PUT or DELETE") + ret, err := codes.ToCode(code) + if err != nil { + return 0, err + } + switch ret { + case codes.GET, codes.POST, codes.PUT, codes.DELETE: + return ret, nil + default: + return 0, errCodeNotSupported + } } func printMsg(m *pool.Message) { @@ -70,11 +86,12 @@ func main() { } help := strings.ToLower(os.Args[1]) if help == "-h" || help == "--help" { - log.Println(helpMsg) - os.Exit(0) + log.Print(helpMsg) + return } - - code, err := parseCode(strings.ToUpper(os.Args[1])) + req := request{} + var err error + req.code, err = parseCode(strings.ToUpper(os.Args[1])) if err != nil { log.Fatalf("Can't read request code: %s\n%s", err, helpCmd) } @@ -82,55 +99,69 @@ func main() { if len(os.Args) < 3 { log.Fatalf("CoAP URL must not be empty.\n%s", helpCmd) } - path := os.Args[2] - if strings.HasPrefix(path, "-") { + req.path = os.Args[2] + if strings.HasPrefix(req.path, "-") { log.Fatalf("Please enter a valid CoAP URL.\n%s", helpCmd) } os.Args = os.Args[2:] - o := flag.Bool("o", false, "Observe") - h := flag.String("h", "localhost", "Host") - p := flag.String("p", "5683", "Port") + req.obs = flag.Bool("o", false, "Observe") + req.host = flag.String("h", "localhost", "Host") + req.port = flag.String("p", "5683", "Port") // Default type is JSON. - cf := flag.Int("cf", 50, "Content format") - d := flag.String("d", "", "Message data") - a := flag.String("auth", "", "Auth token") + req.cf = flag.Int("cf", 50, "Content format") + req.data = flag.String("d", "", "Message data") + req.auth = flag.String("auth", "", "Auth token") flag.Parse() - client, err := coap.New(*h + ":" + *p) + if err = makeRequest(req); err != nil { + log.Fatal(err) + } +} + +func makeRequest(req request) error { + client, err := coap.New(*req.host + ":" + *req.port) if err != nil { - log.Fatal("Error creating client: ", err) + return errors.Join(errCreateClient, err) } var opts coapmsg.Options - if a != nil { - opts = append(opts, coapmsg.Option{ID: coapmsg.URIQuery, Value: []byte(fmt.Sprintf("auth=%s", *a))}) + if req.auth != nil { + opts = append(opts, coapmsg.Option{ID: coapmsg.URIQuery, Value: []byte(fmt.Sprintf("auth=%s", *req.auth))}) } - if o == nil || (!*o) { - pld := strings.NewReader(*d) + if req.obs == nil || (!*req.obs) { + pld := strings.NewReader(*req.data) - res, err := client.Send(path, code, message.MediaType(*cf), pld, opts...) + res, err := client.Send(req.path, req.code, message.MediaType(*req.cf), pld, opts...) if err != nil { - log.Fatal("Error sending message: ", err) + return errors.Join(errSendMessage, err) } printMsg(res) - return + return nil } - if code != codes.GET { - log.Fatal("Only GET requests accept observe option.") + if req.code != codes.GET { + return errInvalidObsOpt } - obs, err := client.Receive(path, opts...) + obs, err := client.Receive(req.path, opts...) if err != nil { - log.Fatal("Error observing resource: ", err) + return errors.Join(errFailedObserve, err) } - errs := make(chan error, 2) + + errs := make(chan error, 1) go func() { - c := make(chan os.Signal) - signal.Notify(c, syscall.SIGINT) - errs <- fmt.Errorf("%s", <-c) + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT) + + sig := <-sigChan + errs <- fmt.Errorf("%v", sig) }() err = <-errs - obs.Cancel(context.Background()) - log.Fatal("Observation terminated: ", err) + if err != nil { + return errors.Join(errTerminatedObs, err) + } + if err := obs.Cancel(context.Background()); err != nil { + return errors.Join(errCancelObs, err) + } + return nil } diff --git a/coap/client.go b/coap/client.go index 585fad8..13f2852 100644 --- a/coap/client.go +++ b/coap/client.go @@ -1,3 +1,6 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + package coap import ( @@ -15,6 +18,8 @@ import ( "github.com/plgd-dev/go-coap/v2/udp/message/pool" ) +var errInvalidMsgCode = errors.New("message can be GET, POST, PUT or DELETE") + // Client represents CoAP client. type Client struct { conn *client.ClientConn @@ -44,8 +49,9 @@ func (c Client) Send(path string, msgCode codes.Code, cf message.MediaType, payl return c.conn.Put(ctx, path, cf, payload, opts...) case codes.DELETE: return c.conn.Delete(ctx, path, opts...) + default: + return nil, errInvalidMsgCode } - return nil, errors.New("Invalid message code") } // Receive receives a message. @@ -58,6 +64,7 @@ func (c Client) Receive(path string, opts ...message.Option) (*client.Observatio body, err := res.ReadBody() if err != nil { fmt.Println("Error reading message body: ", err) + return } if len(body) > 0 {