From 57e4f0674a9388186e7adfc1dfb614f75bb87d0f Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Mon, 22 Nov 2021 11:46:23 +0100 Subject: [PATCH 01/27] Fix build tags --- src/util/commands.go | 1 + src/util/commands_mock.go | 1 + 2 files changed, 2 insertions(+) diff --git a/src/util/commands.go b/src/util/commands.go index 5bb0604..a17f2e6 100644 --- a/src/util/commands.go +++ b/src/util/commands.go @@ -1,4 +1,5 @@ //go:build prod +// +build prod package util diff --git a/src/util/commands_mock.go b/src/util/commands_mock.go index b2a18f3..1790b46 100644 --- a/src/util/commands_mock.go +++ b/src/util/commands_mock.go @@ -1,4 +1,5 @@ //go:build !prod +// +build !prod package util From 25cf158c3ac5b6cd94e14519a66498fb5cd09fdc Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Tue, 23 Nov 2021 18:12:49 +0100 Subject: [PATCH 02/27] a11y fixes --- ui/src/components/Upload/Updater.tsx | 2 +- ui/src/components/app.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/components/Upload/Updater.tsx b/ui/src/components/Upload/Updater.tsx index 49057b1..772f837 100644 --- a/ui/src/components/Upload/Updater.tsx +++ b/ui/src/components/Upload/Updater.tsx @@ -215,7 +215,7 @@ export default class Updater extends Component { progress={circlePercentage} color={circleColor} > - diff --git a/ui/src/components/app.tsx b/ui/src/components/app.tsx index 25d26db..ad564b9 100644 --- a/ui/src/components/app.tsx +++ b/ui/src/components/app.tsx @@ -7,7 +7,7 @@ export default class App extends Component { render() { return (
- + SEBRAUC {version}
From 8c1fd2a6ab84de502078aff03d125e694c27661c Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 21 Nov 2021 23:37:24 +0100 Subject: [PATCH 03/27] Fix makefile --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ed465a0..9861398 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,6 @@ UI_DIR=./ui VERSION=$(shell git tag --sort=-version:refname | head -n 1) setup: - go get -t ./src/... cd ${UI_DIR} && pnpm install test: @@ -17,3 +16,7 @@ build-server: go build -tags prod -ldflags "-s -w -X code.thetadev.de/TSGRain/SEBRAUC/src/util.version=${VERSION}" -o build/sebrauc ./src/. build: build-ui build-server + +clean: + rm -f build/* + rm -rf ${UI_DIR}/dist/** From 3e29e04ac3266629f3d6578e6e72917235129c01 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 2 Dec 2021 20:29:20 +0100 Subject: [PATCH 04/27] Add system info api definition --- .pre-commit-config.yaml | 5 + openapi.yml | 134 ++++++++++++++---- .../components/{Upload => Updater}/Alert.tsx | 0 .../components/{Upload => Updater}/Reboot.tsx | 0 .../{Upload => Updater}/Updater.scss | 0 .../{Upload => Updater}/Updater.tsx | 0 ui/src/components/app.tsx | 2 +- 7 files changed, 115 insertions(+), 26 deletions(-) rename ui/src/components/{Upload => Updater}/Alert.tsx (100%) rename ui/src/components/{Upload => Updater}/Reboot.tsx (100%) rename ui/src/components/{Upload => Updater}/Updater.scss (100%) rename ui/src/components/{Upload => Updater}/Updater.tsx (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf556ef..91ff3f8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,6 +10,11 @@ repos: rev: v2.4.1 hooks: - id: prettier + - repo: https://github.com/dudefellah/pre-commit-openapi + rev: "v0.0.1" + hooks: + - id: check-openapi + - repo: local hooks: - id: tsc diff --git a/openapi.yml b/openapi.yml index a9332ce..f55550d 100644 --- a/openapi.yml +++ b/openapi.yml @@ -1,27 +1,48 @@ -openapi: "3.0.3" +openapi: 3.0.3 info: title: SEBRAUC - version: "0.0.1" + version: 0.1.0 + description: REST API for the SEBRAUC firmware updater servers: - url: http://localhost:8080/api paths: /status: get: + operationId: getStatus responses: - "200": + 200: description: OK content: - "application/json": + application/json: schema: $ref: "#/components/schemas/RaucStatus" default: - description: "Server error" + description: Server error content: - "application/json": + application/json: schema: $ref: "#/components/schemas/StatusMessage" + + /info: + get: + operationId: getInfo + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/SystemInfo" + default: + description: Server error + content: + application/json: + schema: + $ref: "#/components/schemas/StatusMessage" + /update: post: + operationId: startUpdate requestBody: content: multipart/form-data: @@ -32,32 +53,33 @@ paths: type: string format: binary responses: - "200": - description: "OK" + 200: + description: OK content: - "application/json": + application/json: schema: $ref: "#/components/schemas/StatusMessage" default: - description: "Server error" + description: Server error content: - "application/json": + application/json: schema: $ref: "#/components/schemas/StatusMessage" /reboot: post: + operationId: startReboot responses: - "200": - description: "OK" + 200: + description: OK content: - "application/json": + application/json: schema: $ref: "#/components/schemas/StatusMessage" default: - description: "Server error" + description: Server error content: - "application/json": + application/json: schema: $ref: "#/components/schemas/StatusMessage" @@ -67,23 +89,23 @@ components: type: object properties: installing: - description: "True if the installer is running" + description: True if the installer is running type: boolean percent: - description: "Installation progress" + description: Installation progress type: integer minimum: 0 maximum: 100 message: - description: "Current installation step" + description: Current installation step type: string - example: "Copying image to rootfs.0" + example: Copying image to rootfs.0 last_error: - description: "Installation error message" + description: Installation error message type: string example: "Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?" log: - description: "Full command line output of the current installation" + description: Full command line output of the current installation type: string example: "0% Installing\n0% Determining slot states\n20% Determining slot states done.\n" required: @@ -93,15 +115,77 @@ components: - last_error - log + SystemInfo: + type: object + properties: + os_name: + description: Name of the os distribution + type: string + example: "Poky" + os_version: + description: Operating system version + type: string + example: "1.0.2" + uptime: + description: System uptime in seconds + type: integer + example: 5832 + rauc_compatible: + description: Compatible firmware name + type: string + example: "Poky" + rauc_variant: + description: Compatible firmware variant + type: string + example: "rpi-prod" + rauc_booted: + description: Currently booted rootfs + type: string + example: "rootfs.0" + rauc_boot_primary: + description: Primary rootfs to boot from + type: string + example: "rootfs.1" + rauc_rootfs: + description: List of RAUC root filesystems + type: object + additionalProperties: + $ref: "#/components/schemas/RaucFS" + + RaucFS: + type: object + properties: + device: + description: Block device + type: string + example: "/dev/mmcblk0p2" + type: + description: Filesystem + type: string + example: ext4 + state: + description: Current state of filesystem + type: string + enum: [active, inactive, booted] + example: booted + mountpoint: + description: Mount path (null when not mounted) + type: string + nullable: true + example: "/" + bootable: + description: "Is the filesystem bootable" + type: boolean + StatusMessage: type: object properties: success: - description: "Is operation successful" + description: Is operation successful type: boolean msg: - description: "Success message" + description: Success message type: string - example: "Update started" + example: Update started required: - msg diff --git a/ui/src/components/Upload/Alert.tsx b/ui/src/components/Updater/Alert.tsx similarity index 100% rename from ui/src/components/Upload/Alert.tsx rename to ui/src/components/Updater/Alert.tsx diff --git a/ui/src/components/Upload/Reboot.tsx b/ui/src/components/Updater/Reboot.tsx similarity index 100% rename from ui/src/components/Upload/Reboot.tsx rename to ui/src/components/Updater/Reboot.tsx diff --git a/ui/src/components/Upload/Updater.scss b/ui/src/components/Updater/Updater.scss similarity index 100% rename from ui/src/components/Upload/Updater.scss rename to ui/src/components/Updater/Updater.scss diff --git a/ui/src/components/Upload/Updater.tsx b/ui/src/components/Updater/Updater.tsx similarity index 100% rename from ui/src/components/Upload/Updater.tsx rename to ui/src/components/Updater/Updater.tsx diff --git a/ui/src/components/app.tsx b/ui/src/components/app.tsx index ad564b9..4d0f547 100644 --- a/ui/src/components/app.tsx +++ b/ui/src/components/app.tsx @@ -1,5 +1,5 @@ import {Component} from "preact" -import Updater from "./Upload/Updater" +import Updater from "./Updater/Updater" import logo from "../assets/logo.svg" import {version} from "../util/version" From 85c0073651ae74cfffc2e07a3ea55cacc7224bd6 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 3 Dec 2021 00:49:20 +0100 Subject: [PATCH 05/27] Add sysinfo api endpoint --- go.sum | 1 + openapi.yml | 4 +- src/fixtures/rauc_mock/main.go | 28 +++-- src/fixtures/testfiles/os-release | 5 + src/rauc/rauc.go | 2 +- src/server/server.go | 13 +++ src/sysinfo/sysinfo.go | 169 ++++++++++++++++++++++++++++++ src/sysinfo/sysinfo_test.go | 120 +++++++++++++++++++++ src/util/commands.go | 2 +- src/util/commands_mock.go | 2 +- 10 files changed, 333 insertions(+), 13 deletions(-) create mode 100644 src/fixtures/testfiles/os-release create mode 100644 src/sysinfo/sysinfo.go create mode 100644 src/sysinfo/sysinfo_test.go diff --git a/go.sum b/go.sum index 6bcb5c4..4188720 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4 h1:ocK/D6lCgLji37Z2so4xhMl46se1ntReQQCUIU4BWI8= github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/openapi.yml b/openapi.yml index f55550d..55c362c 100644 --- a/openapi.yml +++ b/openapi.yml @@ -150,9 +150,9 @@ components: description: List of RAUC root filesystems type: object additionalProperties: - $ref: "#/components/schemas/RaucFS" + $ref: "#/components/schemas/Rootfs" - RaucFS: + Rootfs: type: object properties: device: diff --git a/src/fixtures/rauc_mock/main.go b/src/fixtures/rauc_mock/main.go index 4c9b884..b87aadb 100644 --- a/src/fixtures/rauc_mock/main.go +++ b/src/fixtures/rauc_mock/main.go @@ -41,22 +41,34 @@ LastError: Failed to check bundle identifier: Invalid identifier. ` + idle Installing ` + "/app/demo` failed" +const statusJson = `{"compatible":"TSGRain","variant":"","booted":"A",` + + `"boot_primary":"rootfs.0","slots":[{"rootfs.1":{"class":"rootfs",` + + `"device":"/dev/mmcblk0p3","type":"ext4","bootname":"B","state":"inactive",` + + `"parent":null,"mountpoint":null,"boot_status":"good"}},{"rootfs.0":` + + `{"class":"rootfs","device":"/dev/mmcblk0p2","type":"ext4","bootname":"A",` + + `"state":"booted","parent":null,"mountpoint":"/","boot_status":"good"}}]}` + +func printLinesWithDelay(lines string) { + for _, line := range strings.Split(lines, "\n") { + fmt.Println(line) + time.Sleep(500 * time.Millisecond) + } +} + func main() { arg := "" if len(os.Args) > 1 { arg = os.Args[1] } - var lines string switch arg { case "fail": - lines = outputFailure + printLinesWithDelay(outputFailure) + case "install": + printLinesWithDelay(outputSuccess) + case "status": + fmt.Println(statusJson) default: - lines = outputSuccess - } - - for _, line := range strings.Split(lines, "\n") { - fmt.Println(line) - time.Sleep(500 * time.Millisecond) + os.Exit(1) } } diff --git a/src/fixtures/testfiles/os-release b/src/fixtures/testfiles/os-release new file mode 100644 index 0000000..84a41a1 --- /dev/null +++ b/src/fixtures/testfiles/os-release @@ -0,0 +1,5 @@ +ID=tsgrain +NAME="TSGRain distro" +VERSION="0.0.1" +VERSION_ID=0.0.1 +PRETTY_NAME="TSGRain distro 0.0.1" diff --git a/src/rauc/rauc.go b/src/rauc/rauc.go index 7585285..3e021fe 100644 --- a/src/rauc/rauc.go +++ b/src/rauc/rauc.go @@ -70,7 +70,7 @@ func (r *Rauc) RunRauc(updateFile string) error { } r.broadcast <- r.GetStatusJson() - cmd := util.CommandFromString(fmt.Sprintf("%s %s", util.UpdateCmd, updateFile)) + cmd := util.CommandFromString(fmt.Sprintf("%s install %s", util.RaucCmd, updateFile)) readPipe, _ := cmd.StdoutPipe() cmd.Stderr = cmd.Stdout diff --git a/src/server/server.go b/src/server/server.go index b9ca070..46522a3 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -8,6 +8,7 @@ import ( "time" "code.thetadev.de/TSGRain/SEBRAUC/src/rauc" + "code.thetadev.de/TSGRain/SEBRAUC/src/sysinfo" "code.thetadev.de/TSGRain/SEBRAUC/src/util" "code.thetadev.de/TSGRain/SEBRAUC/ui" "github.com/gofiber/fiber/v2" @@ -88,6 +89,7 @@ func (srv *SEBRAUCServer) Run() error { app.Get("/api/ws", websocket.New(srv.hub.Handler)) app.Post("/api/update", srv.controllerUpdate) app.Get("/api/status", srv.controllerStatus) + app.Get("/api/info", srv.controllerInfo) app.Post("/api/reboot", srv.controllerReboot) // Start messaging hub @@ -131,6 +133,17 @@ func (srv *SEBRAUCServer) controllerStatus(c *fiber.Ctx) error { return nil } +func (srv *SEBRAUCServer) controllerInfo(c *fiber.Ctx) error { + info, err := sysinfo.GetSysinfo() + if err != nil { + return err + } + + c.Context().SetStatusCode(200) + _ = c.JSON(info) + return nil +} + func (srv *SEBRAUCServer) controllerReboot(c *fiber.Ctx) error { go util.Reboot(5 * time.Second) diff --git a/src/sysinfo/sysinfo.go b/src/sysinfo/sysinfo.go new file mode 100644 index 0000000..dc1df3b --- /dev/null +++ b/src/sysinfo/sysinfo.go @@ -0,0 +1,169 @@ +package sysinfo + +import ( + "encoding/json" + "os" + "regexp" + "strconv" + + "code.thetadev.de/TSGRain/SEBRAUC/src/util" +) + +type SystemInfo struct { + OsName string `json:"os_name"` + OsVersion string `json:"os_version"` + Uptime int `json:"uptime"` + RaucCompatible string `json:"rauc_compatible"` + RaucVariant string `json:"rauc_variant"` + RaucBooted string `json:"rauc_booted"` + RaucBootPrimary string `json:"rauc_boot_primary"` + RaucRootfs map[string]Rootfs `json:"rauc_rootfs"` +} + +type Rootfs struct { + Device string `json:"device"` + Type string `json:"type"` + bootname string + State string `json:"state"` + Mountpoint string `json:"mountpoint"` + Bootable bool `json:"bootable"` +} + +type raucInfo struct { + Compatible string `json:"compatible"` + Variant string `json:"variant"` + Booted string `json:"booted"` + BootPrimary string `json:"boot_primary"` + Slots []map[string]raucFS `json:"slots"` +} + +type raucFS struct { + Class string `json:"class"` + Device string `json:"device"` + Type string `json:"type"` + Bootname string `json:"bootname"` + State string `json:"state"` + Mountpoint string `json:"mountpoint"` + BootStatus string `json:"boot_status"` +} + +type osRelease struct { + OsName string `json:"os_name"` + OsVersion string `json:"os_version"` +} + +var ( + rexpOsName = regexp.MustCompile(`(?m)^NAME="(.+)"`) + rexpOsVersion = regexp.MustCompile(`(?m)^VERSION="(.+)"`) + rexpUptime = regexp.MustCompile(`^\d+`) +) + +func parseRaucInfo(raucInfoJson []byte) (raucInfo, error) { + res := raucInfo{} + err := json.Unmarshal(raucInfoJson, &res) + return res, err +} + +func parseOsRelease(osReleaseFile string) (osRelease, error) { + osReleaseTxt, err := os.ReadFile(osReleaseFile) + if err != nil { + return osRelease{}, err + } + + nameMatch := rexpOsName.FindSubmatch(osReleaseTxt) + versionMatch := rexpOsVersion.FindSubmatch(osReleaseTxt) + + name := "" + if nameMatch != nil { + name = string(nameMatch[1]) + } + + version := "" + if versionMatch != nil { + version = string(versionMatch[1]) + } + + return osRelease{ + OsName: name, + OsVersion: version, + }, nil +} + +func mapRootfs(slots []map[string]raucFS) map[string]Rootfs { + res := make(map[string]Rootfs) + + for _, slot := range slots { + for name, fs := range slot { + if fs.Class == "rootfs" { + res[name] = Rootfs{ + Device: fs.Device, + Type: fs.Type, + bootname: fs.Bootname, + State: fs.State, + Mountpoint: fs.Mountpoint, + Bootable: fs.BootStatus == "good", + } + } + } + } + return res +} + +func getFSNameFromBootname(rfslist map[string]Rootfs, bootname string) string { + for name, rfs := range rfslist { + if rfs.bootname == bootname { + return name + } + } + return "n/a" +} + +func mapSysinfo(rinf raucInfo, osr osRelease, uptime int) SystemInfo { + rfslist := mapRootfs(rinf.Slots) + + return SystemInfo{ + OsName: osr.OsName, + OsVersion: osr.OsVersion, + Uptime: uptime, + RaucCompatible: rinf.Compatible, + RaucVariant: rinf.Variant, + RaucBooted: getFSNameFromBootname(rfslist, rinf.Booted), + RaucBootPrimary: rinf.BootPrimary, + RaucRootfs: rfslist, + } +} + +func getUptime() (int, error) { + uptimeRaw, err := os.ReadFile("/proc/uptime") + if err != nil { + return 0, err + } + + uptimeChars := rexpUptime.Find(uptimeRaw) + return strconv.Atoi(string(uptimeChars)) +} + +func GetSysinfo() (SystemInfo, error) { + cmd := util.CommandFromString(util.RaucCmd + " status") + rinfJson, err := cmd.Output() + if err != nil { + return SystemInfo{}, err + } + + rinf, err := parseRaucInfo(rinfJson) + if err != nil { + return SystemInfo{}, err + } + + osinf, err := parseOsRelease("/etc/os-release") + if err != nil { + return SystemInfo{}, err + } + + uptime, err := getUptime() + if err != nil { + return SystemInfo{}, err + } + + return mapSysinfo(rinf, osinf, uptime), nil +} diff --git a/src/sysinfo/sysinfo_test.go b/src/sysinfo/sysinfo_test.go new file mode 100644 index 0000000..76bf2e0 --- /dev/null +++ b/src/sysinfo/sysinfo_test.go @@ -0,0 +1,120 @@ +package sysinfo + +import ( + "path/filepath" + "testing" + + "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures" + "github.com/stretchr/testify/assert" +) + +const statusJson = `{"compatible":"TSGRain","variant":"dev","booted":"A",` + + `"boot_primary":"rootfs.0","slots":[{"rootfs.1":{"class":"rootfs",` + + `"device":"/dev/mmcblk0p3","type":"ext4","bootname":"B","state":"inactive",` + + `"parent":null,"mountpoint":null,"boot_status":"good"}},{"rootfs.0":` + + `{"class":"rootfs","device":"/dev/mmcblk0p2","type":"ext4","bootname":"A",` + + `"state":"booted","parent":null,"mountpoint":"/","boot_status":"good"}}]}` + +var expectedRaucInfo = raucInfo{ + Compatible: "TSGRain", + Variant: "dev", + Booted: "A", + BootPrimary: "rootfs.0", + Slots: []map[string]raucFS{ + { + "rootfs.1": { + Class: "rootfs", + Device: "/dev/mmcblk0p3", + Type: "ext4", + Bootname: "B", + State: "inactive", + Mountpoint: "", + BootStatus: "good", + }, + }, + { + "rootfs.0": { + Class: "rootfs", + Device: "/dev/mmcblk0p2", + Type: "ext4", + Bootname: "A", + State: "booted", + Mountpoint: "/", + BootStatus: "good", + }, + }, + }, +} + +var expectedRootfsList = map[string]Rootfs{ + "rootfs.0": { + Device: "/dev/mmcblk0p2", + Type: "ext4", + bootname: "A", + State: "booted", + Mountpoint: "/", + Bootable: true, + }, + "rootfs.1": { + Device: "/dev/mmcblk0p3", + Type: "ext4", + bootname: "B", + State: "inactive", + Mountpoint: "", + Bootable: true, + }, +} + +func TestParseRaucInfo(t *testing.T) { + info, err := parseRaucInfo([]byte(statusJson)) + if err != nil { + panic(err) + } + + assert.Equal(t, expectedRaucInfo, info) +} + +func TestParseOsRelease(t *testing.T) { + testfiles := fixtures.GetTestfilesDir() + osReleaseFile := filepath.Join(testfiles, "os-release") + + osRel, err := parseOsRelease(osReleaseFile) + if err != nil { + panic(err) + } + + expected := osRelease{ + OsName: "TSGRain distro", + OsVersion: "0.0.1", + } + + assert.Equal(t, expected, osRel) +} + +func TestMapRootfsList(t *testing.T) { + rootfsList := mapRootfs(expectedRaucInfo.Slots) + + assert.Equal(t, expectedRootfsList, rootfsList) +} + +func TestGetFSNameFromBootname(t *testing.T) { + rootfsList := mapRootfs(expectedRaucInfo.Slots) + + assert.Equal(t, "rootfs.0", getFSNameFromBootname(rootfsList, "A")) + assert.Equal(t, "rootfs.1", getFSNameFromBootname(rootfsList, "B")) + assert.Equal(t, "n/a", getFSNameFromBootname(rootfsList, "C")) +} + +func TestGetSysinfo(t *testing.T) { + sysinfo, err := GetSysinfo() + if err != nil { + panic(err) + } + + assert.Greater(t, sysinfo.Uptime, 0) + assert.Equal(t, "TSGRain", sysinfo.RaucCompatible) + assert.Equal(t, "", sysinfo.RaucVariant) + assert.Equal(t, "rootfs.0", sysinfo.RaucBooted) + assert.Equal(t, "rootfs.0", sysinfo.RaucBootPrimary) + assert.Equal(t, expectedRootfsList, sysinfo.RaucRootfs) +} diff --git a/src/util/commands.go b/src/util/commands.go index a17f2e6..a157fa0 100644 --- a/src/util/commands.go +++ b/src/util/commands.go @@ -5,7 +5,7 @@ package util const ( RebootCmd = "shutdown -r 0" - UpdateCmd = "rauc install" + RaucCmd = "rauc" TestMode = false ) diff --git a/src/util/commands_mock.go b/src/util/commands_mock.go index 1790b46..d4d1392 100644 --- a/src/util/commands_mock.go +++ b/src/util/commands_mock.go @@ -5,7 +5,7 @@ package util const ( RebootCmd = "touch /tmp/sebrauc_reboot_test" - UpdateCmd = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock" + RaucCmd = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock" TestMode = true ) From 7465ef3380c8ecba4125cb76d6bf21ee2376e2eb Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Mon, 6 Dec 2021 00:52:14 +0100 Subject: [PATCH 06/27] add system info, openapi-generated client --- Makefile | 3 + openapi.yml | 34 +- src/fixtures/rauc_mock/main.go | 2 +- src/sysinfo/sysinfo.go | 58 +- src/sysinfo/sysinfo_test.go | 24 +- ui/src/components/Updater/Alert.tsx | 3 +- ui/src/components/Updater/Reboot.tsx | 11 +- ui/src/components/Updater/SysinfoCard.tsx | 159 ++++++ ui/src/components/Updater/Updater.scss | 22 +- .../Updater/{Updater.tsx => UpdaterCard.tsx} | 31 +- ui/src/components/Updater/UpdaterView.tsx | 48 ++ ui/src/components/app.tsx | 4 +- .../sebrauc-client/.openapi-generator-ignore | 27 + .../sebrauc-client/.openapi-generator/FILES | 5 + .../sebrauc-client/.openapi-generator/VERSION | 1 + ui/src/sebrauc-client/api.ts | 533 ++++++++++++++++++ ui/src/sebrauc-client/base.ts | 74 +++ ui/src/sebrauc-client/common.ts | 181 ++++++ ui/src/sebrauc-client/configuration.ts | 123 ++++ ui/src/sebrauc-client/index.ts | 16 + ui/src/style/index.scss | 2 + ui/src/style/table.scss | 47 ++ ui/src/util/apiUrls.ts | 10 +- ui/src/util/colors.ts | 7 + ui/src/util/functions.ts | 18 + 25 files changed, 1355 insertions(+), 88 deletions(-) create mode 100644 ui/src/components/Updater/SysinfoCard.tsx rename ui/src/components/Updater/{Updater.tsx => UpdaterCard.tsx} (90%) create mode 100644 ui/src/components/Updater/UpdaterView.tsx create mode 100644 ui/src/sebrauc-client/.openapi-generator-ignore create mode 100644 ui/src/sebrauc-client/.openapi-generator/FILES create mode 100644 ui/src/sebrauc-client/.openapi-generator/VERSION create mode 100644 ui/src/sebrauc-client/api.ts create mode 100644 ui/src/sebrauc-client/base.ts create mode 100644 ui/src/sebrauc-client/common.ts create mode 100644 ui/src/sebrauc-client/configuration.ts create mode 100644 ui/src/sebrauc-client/index.ts create mode 100644 ui/src/style/table.scss create mode 100644 ui/src/util/colors.ts create mode 100644 ui/src/util/functions.ts diff --git a/Makefile b/Makefile index 9861398..c058ed0 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ build-server: build: build-ui build-server +generate-apiclient: + openapi-generator generate -i openapi.yml -g typescript-axios -o ${UI_DIR}/src/sebrauc-client -p "supportsES6=true" + clean: rm -f build/* rm -rf ${UI_DIR}/dist/** diff --git a/openapi.yml b/openapi.yml index 55c362c..d343569 100644 --- a/openapi.yml +++ b/openapi.yml @@ -138,19 +138,18 @@ components: description: Compatible firmware variant type: string example: "rpi-prod" - rauc_booted: - description: Currently booted rootfs - type: string - example: "rootfs.0" - rauc_boot_primary: - description: Primary rootfs to boot from - type: string - example: "rootfs.1" rauc_rootfs: description: List of RAUC root filesystems type: object additionalProperties: $ref: "#/components/schemas/Rootfs" + required: + - os_name + - os_version + - uptime + - rauc_compatible + - rauc_variant + - rauc_rootfs Rootfs: type: object @@ -163,11 +162,6 @@ components: description: Filesystem type: string example: ext4 - state: - description: Current state of filesystem - type: string - enum: [active, inactive, booted] - example: booted mountpoint: description: Mount path (null when not mounted) type: string @@ -176,6 +170,19 @@ components: bootable: description: "Is the filesystem bootable" type: boolean + booted: + description: "Is the filesystem booted" + type: boolean + primary: + description: "Is the filesystem the next boot target" + type: boolean + required: + - device + - type + - mountpoint + - bootable + - booted + - primary StatusMessage: type: object @@ -188,4 +195,5 @@ components: type: string example: Update started required: + - success - msg diff --git a/src/fixtures/rauc_mock/main.go b/src/fixtures/rauc_mock/main.go index b87aadb..58986ff 100644 --- a/src/fixtures/rauc_mock/main.go +++ b/src/fixtures/rauc_mock/main.go @@ -41,7 +41,7 @@ LastError: Failed to check bundle identifier: Invalid identifier. ` + idle Installing ` + "/app/demo` failed" -const statusJson = `{"compatible":"TSGRain","variant":"","booted":"A",` + +const statusJson = `{"compatible":"TSGRain","variant":"dev","booted":"A",` + `"boot_primary":"rootfs.0","slots":[{"rootfs.1":{"class":"rootfs",` + `"device":"/dev/mmcblk0p3","type":"ext4","bootname":"B","state":"inactive",` + `"parent":null,"mountpoint":null,"boot_status":"good"}},{"rootfs.0":` + diff --git a/src/sysinfo/sysinfo.go b/src/sysinfo/sysinfo.go index dc1df3b..57caafe 100644 --- a/src/sysinfo/sysinfo.go +++ b/src/sysinfo/sysinfo.go @@ -10,23 +10,22 @@ import ( ) type SystemInfo struct { - OsName string `json:"os_name"` - OsVersion string `json:"os_version"` - Uptime int `json:"uptime"` - RaucCompatible string `json:"rauc_compatible"` - RaucVariant string `json:"rauc_variant"` - RaucBooted string `json:"rauc_booted"` - RaucBootPrimary string `json:"rauc_boot_primary"` - RaucRootfs map[string]Rootfs `json:"rauc_rootfs"` + OsName string `json:"os_name"` + OsVersion string `json:"os_version"` + Uptime int `json:"uptime"` + RaucCompatible string `json:"rauc_compatible"` + RaucVariant string `json:"rauc_variant"` + RaucRootfs map[string]Rootfs `json:"rauc_rootfs"` } type Rootfs struct { Device string `json:"device"` Type string `json:"type"` bootname string - State string `json:"state"` - Mountpoint string `json:"mountpoint"` - Bootable bool `json:"bootable"` + Mountpoint *string `json:"mountpoint"` + Bootable bool `json:"bootable"` + Booted bool `json:"booted"` + Primary bool `json:"primary"` } type raucInfo struct { @@ -38,13 +37,13 @@ type raucInfo struct { } type raucFS struct { - Class string `json:"class"` - Device string `json:"device"` - Type string `json:"type"` - Bootname string `json:"bootname"` - State string `json:"state"` - Mountpoint string `json:"mountpoint"` - BootStatus string `json:"boot_status"` + Class string `json:"class"` + Device string `json:"device"` + Type string `json:"type"` + Bootname string `json:"bootname"` + State string `json:"state"` + Mountpoint *string `json:"mountpoint"` + BootStatus string `json:"boot_status"` } type osRelease struct { @@ -89,19 +88,20 @@ func parseOsRelease(osReleaseFile string) (osRelease, error) { }, nil } -func mapRootfs(slots []map[string]raucFS) map[string]Rootfs { +func mapRootfs(rinf raucInfo) map[string]Rootfs { res := make(map[string]Rootfs) - for _, slot := range slots { + for _, slot := range rinf.Slots { for name, fs := range slot { if fs.Class == "rootfs" { res[name] = Rootfs{ Device: fs.Device, Type: fs.Type, bootname: fs.Bootname, - State: fs.State, Mountpoint: fs.Mountpoint, Bootable: fs.BootStatus == "good", + Booted: fs.State == "booted", + Primary: rinf.BootPrimary == name, } } } @@ -119,17 +119,15 @@ func getFSNameFromBootname(rfslist map[string]Rootfs, bootname string) string { } func mapSysinfo(rinf raucInfo, osr osRelease, uptime int) SystemInfo { - rfslist := mapRootfs(rinf.Slots) + rfslist := mapRootfs(rinf) return SystemInfo{ - OsName: osr.OsName, - OsVersion: osr.OsVersion, - Uptime: uptime, - RaucCompatible: rinf.Compatible, - RaucVariant: rinf.Variant, - RaucBooted: getFSNameFromBootname(rfslist, rinf.Booted), - RaucBootPrimary: rinf.BootPrimary, - RaucRootfs: rfslist, + OsName: osr.OsName, + OsVersion: osr.OsVersion, + Uptime: uptime, + RaucCompatible: rinf.Compatible, + RaucVariant: rinf.Variant, + RaucRootfs: rfslist, } } diff --git a/src/sysinfo/sysinfo_test.go b/src/sysinfo/sysinfo_test.go index 76bf2e0..3628435 100644 --- a/src/sysinfo/sysinfo_test.go +++ b/src/sysinfo/sysinfo_test.go @@ -15,6 +15,8 @@ const statusJson = `{"compatible":"TSGRain","variant":"dev","booted":"A",` + `{"class":"rootfs","device":"/dev/mmcblk0p2","type":"ext4","bootname":"A",` + `"state":"booted","parent":null,"mountpoint":"/","boot_status":"good"}}]}` +var mountRoot = "/" + var expectedRaucInfo = raucInfo{ Compatible: "TSGRain", Variant: "dev", @@ -28,7 +30,7 @@ var expectedRaucInfo = raucInfo{ Type: "ext4", Bootname: "B", State: "inactive", - Mountpoint: "", + Mountpoint: nil, BootStatus: "good", }, }, @@ -39,7 +41,7 @@ var expectedRaucInfo = raucInfo{ Type: "ext4", Bootname: "A", State: "booted", - Mountpoint: "/", + Mountpoint: &mountRoot, BootStatus: "good", }, }, @@ -51,17 +53,19 @@ var expectedRootfsList = map[string]Rootfs{ Device: "/dev/mmcblk0p2", Type: "ext4", bootname: "A", - State: "booted", - Mountpoint: "/", + Mountpoint: &mountRoot, Bootable: true, + Booted: true, + Primary: true, }, "rootfs.1": { Device: "/dev/mmcblk0p3", Type: "ext4", bootname: "B", - State: "inactive", - Mountpoint: "", + Mountpoint: nil, Bootable: true, + Booted: false, + Primary: false, }, } @@ -92,13 +96,13 @@ func TestParseOsRelease(t *testing.T) { } func TestMapRootfsList(t *testing.T) { - rootfsList := mapRootfs(expectedRaucInfo.Slots) + rootfsList := mapRootfs(expectedRaucInfo) assert.Equal(t, expectedRootfsList, rootfsList) } func TestGetFSNameFromBootname(t *testing.T) { - rootfsList := mapRootfs(expectedRaucInfo.Slots) + rootfsList := mapRootfs(expectedRaucInfo) assert.Equal(t, "rootfs.0", getFSNameFromBootname(rootfsList, "A")) assert.Equal(t, "rootfs.1", getFSNameFromBootname(rootfsList, "B")) @@ -113,8 +117,6 @@ func TestGetSysinfo(t *testing.T) { assert.Greater(t, sysinfo.Uptime, 0) assert.Equal(t, "TSGRain", sysinfo.RaucCompatible) - assert.Equal(t, "", sysinfo.RaucVariant) - assert.Equal(t, "rootfs.0", sysinfo.RaucBooted) - assert.Equal(t, "rootfs.0", sysinfo.RaucBootPrimary) + assert.Equal(t, "dev", sysinfo.RaucVariant) assert.Equal(t, expectedRootfsList, sysinfo.RaucRootfs) } diff --git a/ui/src/components/Updater/Alert.tsx b/ui/src/components/Updater/Alert.tsx index 6eb16a9..204a691 100644 --- a/ui/src/components/Updater/Alert.tsx +++ b/ui/src/components/Updater/Alert.tsx @@ -1,6 +1,7 @@ import {Component} from "preact" import {mdiTriangleOutline} from "@mdi/js" import Icon from "../Icon/Icon" +import colors from "../../util/colors" type Props = { source?: string @@ -20,7 +21,7 @@ export default class Alert extends Component { return (
- + {msg}
diff --git a/ui/src/components/Updater/Reboot.tsx b/ui/src/components/Updater/Reboot.tsx index c53666e..49a0232 100644 --- a/ui/src/components/Updater/Reboot.tsx +++ b/ui/src/components/Updater/Reboot.tsx @@ -1,7 +1,6 @@ import {mdiCheckCircleOutline, mdiRestore} from "@mdi/js" -import axios, {AxiosError, AxiosResponse} from "axios" import {Component} from "preact" -import {apiUrl} from "../../util/apiUrls" +import {sebraucApi} from "../../util/apiUrls" import Icon from "../Icon/Icon" export default class Reboot extends Component { @@ -9,9 +8,9 @@ export default class Reboot extends Component { const res = confirm("Reboot the system?") if (!res) return - axios - .post(apiUrl + "/reboot") - .then((response: AxiosResponse) => { + sebraucApi + .startReboot() + .then((response) => { const msg = response.data.msg if (msg !== undefined) { @@ -20,7 +19,7 @@ export default class Reboot extends Component { alert("No response") } }) - .catch((error: AxiosError) => { + .catch((error) => { if (error.response) { const msg = error.response.data.msg diff --git a/ui/src/components/Updater/SysinfoCard.tsx b/ui/src/components/Updater/SysinfoCard.tsx new file mode 100644 index 0000000..56813a3 --- /dev/null +++ b/ui/src/components/Updater/SysinfoCard.tsx @@ -0,0 +1,159 @@ +import {Component} from "preact" +import {SystemInfo} from "../../sebrauc-client" +import {sebraucApi} from "../../util/apiUrls" +import {secondsToString} from "../../util/functions" +import Icon from "../Icon/Icon" +import { + mdiAlphaVCircleOutline, + mdiCheckCircleOutline, + mdiCircleOutline, + mdiClockOutline, + mdiCloseCircleOutline, + mdiMonitor, + mdiPenguin, + mdiTagMultipleOutline, + mdiTagOutline, +} from "@mdi/js" +import colors from "../../util/colors" + +type Props = {} + +type State = { + sysinfo: SystemInfo +} + +export default class SysinfoCard extends Component { + constructor(props?: Props | undefined, context?: any) { + super(props, context) + this.fetchInfo() + } + + private fetchInfo = () => { + sebraucApi + .getInfo() + .then((response) => { + if (response.status == 200) { + this.setState({sysinfo: response.data}) + } else { + console.log("error fetching info", response.data) + console.log("error fetching info", response.data) + window.setTimeout(this.fetchInfo, 3000) + } + }) + .catch((reason) => { + console.log("error fetching info", reason) + window.setTimeout(this.fetchInfo, 3000) + }) + } + + private renderSysinfo() { + return ( +
+
+

System information

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Hostname + TODO
+ Operating system + {this.state.sysinfo.os_name}
+ OS version + {this.state.sysinfo.os_version}
+ Uptime + {secondsToString(this.state.sysinfo.uptime)}
+ Compatible FW + {this.state.sysinfo.rauc_compatible}
+ Compatible FW + variant + {this.state.sysinfo.rauc_variant}
+
+ +
+

Rootfs slots

+
+ + + + + + + + + + + {Object.keys(this.state.sysinfo.rauc_rootfs).map( + (k, i) => { + const rfs = this.state.sysinfo.rauc_rootfs[k] + let icon = mdiCircleOutline + let iconColor = colors.BLUE + + if (!rfs.bootable) { + icon = mdiCloseCircleOutline + iconColor = colors.RED + } else if (rfs.primary) { + icon = mdiCheckCircleOutline + iconColor = colors.GREEN + } + + return ( + + + + + + + ) + } + )} + +
NameDeviceMountpoint
+ + {k}{rfs.device}{rfs.mountpoint}
+
+
+
+ ) + } + + private renderLoadingAnimation() { + return ( +
+

loading sysinfo...

+
+ ) + } + + render() { + if (this.state.sysinfo) { + return this.renderSysinfo() + } + return this.renderLoadingAnimation() + } +} diff --git a/ui/src/components/Updater/Updater.scss b/ui/src/components/Updater/Updater.scss index 90b798e..fe3ace9 100644 --- a/ui/src/components/Updater/Updater.scss +++ b/ui/src/components/Updater/Updater.scss @@ -1,11 +1,11 @@ -.uploader { +.updater-view { display: flex; flex-direction: column; justify-content: center; align-items: center; margin: 0 auto; - max-width: 500px; + max-width: 600px; width: 90%; > * { @@ -18,8 +18,8 @@ justify-content: center; align-items: center; - padding: 15px 8px; - margin: 8px 0; + margin-top: 25px; + margin-bottom: 8px; text-align: center; @@ -29,6 +29,14 @@ .top { font-size: 1.5em; } + + &.pad { + padding: 15px 0; + } + + &:first-of-type { + margin-top: 8px; + } } .alert { @@ -42,3 +50,9 @@ } } } + +.button-top-right { + position: absolute; + top: 20px; + right: 20px; +} diff --git a/ui/src/components/Updater/Updater.tsx b/ui/src/components/Updater/UpdaterCard.tsx similarity index 90% rename from ui/src/components/Updater/Updater.tsx rename to ui/src/components/Updater/UpdaterCard.tsx index 772f837..6873897 100644 --- a/ui/src/components/Updater/Updater.tsx +++ b/ui/src/components/Updater/UpdaterCard.tsx @@ -5,10 +5,10 @@ import Dropzone from "../Dropzone/Dropzone" import ProgressCircle from "../ProgressCircle/ProgressCircle" import Icon from "../Icon/Icon" import "./Updater.scss" -import axios from "axios" import Alert from "./Alert" import Reboot from "./Reboot" -import {apiUrl, wsUrl} from "../../util/apiUrls" +import {sebraucApi, wsUrl} from "../../util/apiUrls" +import colors from "../../util/colors" class UploadStatus { uploading = false @@ -50,7 +50,7 @@ type State = { wsConnected: boolean } -export default class Updater extends Component { +export default class UpdaterCard extends Component { private dropzoneRef = createRef() private conn: WebSocket | undefined @@ -77,19 +77,13 @@ export default class Updater extends Component { if (files.length === 0) return const newFile = files[0] - const formData = new FormData() - formData.append("updateFile", newFile) - this.setState({ uploadStatus: new UploadStatus(true, newFile.size, 0), uploadFilename: newFile.name, }) - axios - .post(apiUrl + "/update", formData, { - headers: { - "Content-Type": "multipart/form-data", - }, + sebraucApi + .startUpdate(newFile, { onUploadProgress: (progressEvent: {loaded: number; total: number}) => { this.setState({ uploadStatus: UploadStatus.fromProgressEvent(progressEvent), @@ -132,8 +126,6 @@ export default class Updater extends Component { JSON.parse(messages[i]) ), }) - - console.log(this.state.raucStatus) } } } else { @@ -163,9 +155,9 @@ export default class Updater extends Component { } private circleColor(): string { - if (this.state.raucStatus.installing) return "#FF0039" - if (this.state.uploadStatus.uploading) return "#148420" - return "#1f85de" + if (this.state.raucStatus.installing) return colors.RED + if (this.state.uploadStatus.uploading) return colors.GREEN + return colors.BLUE } private circlePercentage(): number { @@ -195,12 +187,13 @@ export default class Updater extends Component { topText = "Updating firmware" bottomText = this.state.raucStatus.message } else { - topText = "Upload firmware package" + topText = "Firmware update" + bottomText = "Upload *.raucb FW package" } return ( -
-
+
+

{topText}

diff --git a/ui/src/components/Updater/UpdaterView.tsx b/ui/src/components/Updater/UpdaterView.tsx new file mode 100644 index 0000000..faf96c4 --- /dev/null +++ b/ui/src/components/Updater/UpdaterView.tsx @@ -0,0 +1,48 @@ +import {mdiInformation, mdiUpload} from "@mdi/js" +import {Component} from "preact" +import Icon from "../Icon/Icon" +import SysinfoCard from "./SysinfoCard" +import UpdaterCard from "./UpdaterCard" +import "./Updater.scss" + +type Props = {} + +type State = { + flipped: boolean +} + +export default class UpdaterView extends Component { + constructor(props?: Props | undefined, context?: any) { + super(props, context) + + this.state = { + flipped: false, + } + } + + private flipCard = () => { + this.setState({flipped: !this.state.flipped}) + } + + render() { + return ( +
+ + +
+ {!this.state.flipped ? : } +
+
+ ) + } +} diff --git a/ui/src/components/app.tsx b/ui/src/components/app.tsx index 4d0f547..b068bc3 100644 --- a/ui/src/components/app.tsx +++ b/ui/src/components/app.tsx @@ -1,5 +1,5 @@ import {Component} from "preact" -import Updater from "./Updater/Updater" +import UpdaterView from "./Updater/UpdaterView" import logo from "../assets/logo.svg" import {version} from "../util/version" @@ -9,7 +9,7 @@ export default class App extends Component {
SEBRAUC {version} - +
) } diff --git a/ui/src/sebrauc-client/.openapi-generator-ignore b/ui/src/sebrauc-client/.openapi-generator-ignore new file mode 100644 index 0000000..7e15242 --- /dev/null +++ b/ui/src/sebrauc-client/.openapi-generator-ignore @@ -0,0 +1,27 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md + +/.gitignore +/.npmignore +/git_push.sh diff --git a/ui/src/sebrauc-client/.openapi-generator/FILES b/ui/src/sebrauc-client/.openapi-generator/FILES new file mode 100644 index 0000000..53250c0 --- /dev/null +++ b/ui/src/sebrauc-client/.openapi-generator/FILES @@ -0,0 +1,5 @@ +api.ts +base.ts +common.ts +configuration.ts +index.ts diff --git a/ui/src/sebrauc-client/.openapi-generator/VERSION b/ui/src/sebrauc-client/.openapi-generator/VERSION new file mode 100644 index 0000000..e230c83 --- /dev/null +++ b/ui/src/sebrauc-client/.openapi-generator/VERSION @@ -0,0 +1 @@ +5.3.0 \ No newline at end of file diff --git a/ui/src/sebrauc-client/api.ts b/ui/src/sebrauc-client/api.ts new file mode 100644 index 0000000..65eaf45 --- /dev/null +++ b/ui/src/sebrauc-client/api.ts @@ -0,0 +1,533 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * SEBRAUC + * REST API for the SEBRAUC firmware updater + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import {Configuration} from "./configuration" +import globalAxios, {AxiosPromise, AxiosInstance, AxiosRequestConfig} from "axios" +// Some imports not used depending on template conditions +// @ts-ignore +import { + DUMMY_BASE_URL, + assertParamExists, + setApiKeyToObject, + setBasicAuthToObject, + setBearerAuthToObject, + setOAuthToObject, + setSearchParams, + serializeDataIfNeeded, + toPathString, + createRequestFunction, +} from "./common" +// @ts-ignore +import { + BASE_PATH, + COLLECTION_FORMATS, + RequestArgs, + BaseAPI, + RequiredError, +} from "./base" + +/** + * + * @export + * @interface RaucStatus + */ +export interface RaucStatus { + /** + * True if the installer is running + * @type {boolean} + * @memberof RaucStatus + */ + installing: boolean + /** + * Installation progress + * @type {number} + * @memberof RaucStatus + */ + percent: number + /** + * Current installation step + * @type {string} + * @memberof RaucStatus + */ + message: string + /** + * Installation error message + * @type {string} + * @memberof RaucStatus + */ + last_error: string + /** + * Full command line output of the current installation + * @type {string} + * @memberof RaucStatus + */ + log: string +} +/** + * + * @export + * @interface Rootfs + */ +export interface Rootfs { + /** + * Block device + * @type {string} + * @memberof Rootfs + */ + device?: string + /** + * Filesystem + * @type {string} + * @memberof Rootfs + */ + type: string + /** + * Mount path (null when not mounted) + * @type {string} + * @memberof Rootfs + */ + mountpoint: string | null + /** + * Is the filesystem bootable + * @type {boolean} + * @memberof Rootfs + */ + bootable: boolean + /** + * Is the filesystem booted + * @type {boolean} + * @memberof Rootfs + */ + booted: boolean + /** + * Is the filesystem the next boot target + * @type {boolean} + * @memberof Rootfs + */ + primary: boolean +} +/** + * + * @export + * @interface StatusMessage + */ +export interface StatusMessage { + /** + * Is operation successful + * @type {boolean} + * @memberof StatusMessage + */ + success: boolean + /** + * Success message + * @type {string} + * @memberof StatusMessage + */ + msg: string +} +/** + * + * @export + * @interface SystemInfo + */ +export interface SystemInfo { + /** + * Name of the os distribution + * @type {string} + * @memberof SystemInfo + */ + os_name: string + /** + * Operating system version + * @type {string} + * @memberof SystemInfo + */ + os_version: string + /** + * System uptime in seconds + * @type {number} + * @memberof SystemInfo + */ + uptime: number + /** + * Compatible firmware name + * @type {string} + * @memberof SystemInfo + */ + rauc_compatible: string + /** + * Compatible firmware variant + * @type {string} + * @memberof SystemInfo + */ + rauc_variant: string + /** + * List of RAUC root filesystems + * @type {{ [key: string]: Rootfs; }} + * @memberof SystemInfo + */ + rauc_rootfs: {[key: string]: Rootfs} +} + +/** + * DefaultApi - axios parameter creator + * @export + */ +export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getInfo: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/info` + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL) + let baseOptions + if (configuration) { + baseOptions = configuration.baseOptions + } + + const localVarRequestOptions = {method: "GET", ...baseOptions, ...options} + const localVarHeaderParameter = {} as any + const localVarQueryParameter = {} as any + + setSearchParams(localVarUrlObj, localVarQueryParameter) + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {} + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + } + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + } + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getStatus: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/status` + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL) + let baseOptions + if (configuration) { + baseOptions = configuration.baseOptions + } + + const localVarRequestOptions = {method: "GET", ...baseOptions, ...options} + const localVarHeaderParameter = {} as any + const localVarQueryParameter = {} as any + + setSearchParams(localVarUrlObj, localVarQueryParameter) + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {} + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + } + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + } + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + startReboot: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/reboot` + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL) + let baseOptions + if (configuration) { + baseOptions = configuration.baseOptions + } + + const localVarRequestOptions = {method: "POST", ...baseOptions, ...options} + const localVarHeaderParameter = {} as any + const localVarQueryParameter = {} as any + + setSearchParams(localVarUrlObj, localVarQueryParameter) + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {} + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + } + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + } + }, + /** + * + * @param {any} [updateFile] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + startUpdate: async ( + updateFile?: any, + options: AxiosRequestConfig = {} + ): Promise => { + const localVarPath = `/update` + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL) + let baseOptions + if (configuration) { + baseOptions = configuration.baseOptions + } + + const localVarRequestOptions = {method: "POST", ...baseOptions, ...options} + const localVarHeaderParameter = {} as any + const localVarQueryParameter = {} as any + const localVarFormParams = new ((configuration && + configuration.formDataCtor) || + FormData)() + + if (updateFile !== undefined) { + localVarFormParams.append("updateFile", updateFile as any) + } + + localVarHeaderParameter["Content-Type"] = "multipart/form-data" + + setSearchParams(localVarUrlObj, localVarQueryParameter) + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {} + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + } + localVarRequestOptions.data = localVarFormParams + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + } + }, + } +} + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function (configuration?: Configuration) { + const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getInfo( + options?: AxiosRequestConfig + ): Promise< + (axios?: AxiosInstance, basePath?: string) => AxiosPromise + > { + const localVarAxiosArgs = await localVarAxiosParamCreator.getInfo(options) + return createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration + ) + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getStatus( + options?: AxiosRequestConfig + ): Promise< + (axios?: AxiosInstance, basePath?: string) => AxiosPromise + > { + const localVarAxiosArgs = await localVarAxiosParamCreator.getStatus(options) + return createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration + ) + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async startReboot( + options?: AxiosRequestConfig + ): Promise< + (axios?: AxiosInstance, basePath?: string) => AxiosPromise + > { + const localVarAxiosArgs = await localVarAxiosParamCreator.startReboot( + options + ) + return createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration + ) + }, + /** + * + * @param {any} [updateFile] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async startUpdate( + updateFile?: any, + options?: AxiosRequestConfig + ): Promise< + (axios?: AxiosInstance, basePath?: string) => AxiosPromise + > { + const localVarAxiosArgs = await localVarAxiosParamCreator.startUpdate( + updateFile, + options + ) + return createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration + ) + }, + } +} + +/** + * DefaultApi - factory interface + * @export + */ +export const DefaultApiFactory = function ( + configuration?: Configuration, + basePath?: string, + axios?: AxiosInstance +) { + const localVarFp = DefaultApiFp(configuration) + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getInfo(options?: any): AxiosPromise { + return localVarFp + .getInfo(options) + .then((request) => request(axios, basePath)) + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getStatus(options?: any): AxiosPromise { + return localVarFp + .getStatus(options) + .then((request) => request(axios, basePath)) + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + startReboot(options?: any): AxiosPromise { + return localVarFp + .startReboot(options) + .then((request) => request(axios, basePath)) + }, + /** + * + * @param {any} [updateFile] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + startUpdate(updateFile?: any, options?: any): AxiosPromise { + return localVarFp + .startUpdate(updateFile, options) + .then((request) => request(axios, basePath)) + }, + } +} + +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +export class DefaultApi extends BaseAPI { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getInfo(options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration) + .getInfo(options) + .then((request) => request(this.axios, this.basePath)) + } + + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getStatus(options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration) + .getStatus(options) + .then((request) => request(this.axios, this.basePath)) + } + + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public startReboot(options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration) + .startReboot(options) + .then((request) => request(this.axios, this.basePath)) + } + + /** + * + * @param {any} [updateFile] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public startUpdate(updateFile?: any, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration) + .startUpdate(updateFile, options) + .then((request) => request(this.axios, this.basePath)) + } +} diff --git a/ui/src/sebrauc-client/base.ts b/ui/src/sebrauc-client/base.ts new file mode 100644 index 0000000..6248680 --- /dev/null +++ b/ui/src/sebrauc-client/base.ts @@ -0,0 +1,74 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * SEBRAUC + * REST API for the SEBRAUC firmware updater + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import {Configuration} from "./configuration" +// Some imports not used depending on template conditions +// @ts-ignore +import globalAxios, {AxiosPromise, AxiosInstance, AxiosRequestConfig} from "axios" + +export const BASE_PATH = "http://localhost:8080/api".replace(/\/+$/, "") + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +} + +/** + * + * @export + * @interface RequestArgs + */ +export interface RequestArgs { + url: string + options: AxiosRequestConfig +} + +/** + * + * @export + * @class BaseAPI + */ +export class BaseAPI { + protected configuration: Configuration | undefined + + constructor( + configuration?: Configuration, + protected basePath: string = BASE_PATH, + protected axios: AxiosInstance = globalAxios + ) { + if (configuration) { + this.configuration = configuration + this.basePath = configuration.basePath || this.basePath + } + } +} + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + name: "RequiredError" = "RequiredError" + constructor(public field: string, msg?: string) { + super(msg) + } +} diff --git a/ui/src/sebrauc-client/common.ts b/ui/src/sebrauc-client/common.ts new file mode 100644 index 0000000..9942bb6 --- /dev/null +++ b/ui/src/sebrauc-client/common.ts @@ -0,0 +1,181 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * SEBRAUC + * REST API for the SEBRAUC firmware updater + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import {Configuration} from "./configuration" +import {RequiredError, RequestArgs} from "./base" +import {AxiosInstance, AxiosResponse} from "axios" + +/** + * + * @export + */ +export const DUMMY_BASE_URL = "https://example.com" + +/** + * + * @throws {RequiredError} + * @export + */ +export const assertParamExists = function ( + functionName: string, + paramName: string, + paramValue: unknown +) { + if (paramValue === null || paramValue === undefined) { + throw new RequiredError( + paramName, + `Required parameter ${paramName} was null or undefined when calling ${functionName}.` + ) + } +} + +/** + * + * @export + */ +export const setApiKeyToObject = async function ( + object: any, + keyParamName: string, + configuration?: Configuration +) { + if (configuration && configuration.apiKey) { + const localVarApiKeyValue = + typeof configuration.apiKey === "function" + ? await configuration.apiKey(keyParamName) + : await configuration.apiKey + object[keyParamName] = localVarApiKeyValue + } +} + +/** + * + * @export + */ +export const setBasicAuthToObject = function ( + object: any, + configuration?: Configuration +) { + if (configuration && (configuration.username || configuration.password)) { + object["auth"] = { + username: configuration.username, + password: configuration.password, + } + } +} + +/** + * + * @export + */ +export const setBearerAuthToObject = async function ( + object: any, + configuration?: Configuration +) { + if (configuration && configuration.accessToken) { + const accessToken = + typeof configuration.accessToken === "function" + ? await configuration.accessToken() + : await configuration.accessToken + object["Authorization"] = "Bearer " + accessToken + } +} + +/** + * + * @export + */ +export const setOAuthToObject = async function ( + object: any, + name: string, + scopes: string[], + configuration?: Configuration +) { + if (configuration && configuration.accessToken) { + const localVarAccessTokenValue = + typeof configuration.accessToken === "function" + ? await configuration.accessToken(name, scopes) + : await configuration.accessToken + object["Authorization"] = "Bearer " + localVarAccessTokenValue + } +} + +/** + * + * @export + */ +export const setSearchParams = function (url: URL, ...objects: any[]) { + const searchParams = new URLSearchParams(url.search) + for (const object of objects) { + for (const key in object) { + if (Array.isArray(object[key])) { + searchParams.delete(key) + for (const item of object[key]) { + searchParams.append(key, item) + } + } else { + searchParams.set(key, object[key]) + } + } + } + url.search = searchParams.toString() +} + +/** + * + * @export + */ +export const serializeDataIfNeeded = function ( + value: any, + requestOptions: any, + configuration?: Configuration +) { + const nonString = typeof value !== "string" + const needsSerialization = + nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers["Content-Type"]) + : nonString + return needsSerialization + ? JSON.stringify(value !== undefined ? value : {}) + : value || "" +} + +/** + * + * @export + */ +export const toPathString = function (url: URL) { + return url.pathname + url.search + url.hash +} + +/** + * + * @export + */ +export const createRequestFunction = function ( + axiosArgs: RequestArgs, + globalAxios: AxiosInstance, + BASE_PATH: string, + configuration?: Configuration +) { + return >( + axios: AxiosInstance = globalAxios, + basePath: string = BASE_PATH + ) => { + const axiosRequestArgs = { + ...axiosArgs.options, + url: (configuration?.basePath || basePath) + axiosArgs.url, + } + return axios.request(axiosRequestArgs) + } +} diff --git a/ui/src/sebrauc-client/configuration.ts b/ui/src/sebrauc-client/configuration.ts new file mode 100644 index 0000000..357fd9b --- /dev/null +++ b/ui/src/sebrauc-client/configuration.ts @@ -0,0 +1,123 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * SEBRAUC + * REST API for the SEBRAUC firmware updater + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +export interface ConfigurationParameters { + apiKey?: + | string + | Promise + | ((name: string) => string) + | ((name: string) => Promise) + username?: string + password?: string + accessToken?: + | string + | Promise + | ((name?: string, scopes?: string[]) => string) + | ((name?: string, scopes?: string[]) => Promise) + basePath?: string + baseOptions?: any + formDataCtor?: new () => any +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + * @memberof Configuration + */ + apiKey?: + | string + | Promise + | ((name: string) => string) + | ((name: string) => Promise) + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + username?: string + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + password?: string + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + * @memberof Configuration + */ + accessToken?: + | string + | Promise + | ((name?: string, scopes?: string[]) => string) + | ((name?: string, scopes?: string[]) => Promise) + /** + * override base path + * + * @type {string} + * @memberof Configuration + */ + basePath?: string + /** + * base options for axios calls + * + * @type {any} + * @memberof Configuration + */ + baseOptions?: any + /** + * The FormData constructor that will be used to create multipart form data + * requests. You can inject this here so that execution environments that + * do not support the FormData class can still run the generated client. + * + * @type {new () => FormData} + */ + formDataCtor?: new () => any + + constructor(param: ConfigurationParameters = {}) { + this.apiKey = param.apiKey + this.username = param.username + this.password = param.password + this.accessToken = param.accessToken + this.basePath = param.basePath + this.baseOptions = param.baseOptions + this.formDataCtor = param.formDataCtor + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp( + "^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$", + "i" + ) + return ( + mime !== null && + (jsonMime.test(mime) || + mime.toLowerCase() === "application/json-patch+json") + ) + } +} diff --git a/ui/src/sebrauc-client/index.ts b/ui/src/sebrauc-client/index.ts new file mode 100644 index 0000000..a0c08f6 --- /dev/null +++ b/ui/src/sebrauc-client/index.ts @@ -0,0 +1,16 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * SEBRAUC + * REST API for the SEBRAUC firmware updater + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +export * from "./api" +export * from "./configuration" diff --git a/ui/src/style/index.scss b/ui/src/style/index.scss index 5774a08..1f333d4 100644 --- a/ui/src/style/index.scss +++ b/ui/src/style/index.scss @@ -1,3 +1,5 @@ +@use "table"; + html, body { height: 100%; diff --git a/ui/src/style/table.scss b/ui/src/style/table.scss new file mode 100644 index 0000000..c522f5d --- /dev/null +++ b/ui/src/style/table.scss @@ -0,0 +1,47 @@ +.table-wrapper { + width: 100%; + overflow-x: auto; +} + +.table { + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + border-collapse: collapse; + margin: 0; + padding: 0; + width: 100%; + + &.no-bottom-border { + &, + > tr:last-child, + :not(thead) tr:last-child { + border-bottom: none; + } + } +} + +.table caption { + font-size: 1.5em; + margin: 0.5em 0 0.75em; +} + +.table tr { + border-bottom: 1px solid #ddd; + padding: 0.35em; +} + +.table th, +.table td { + padding: 0.625em; + text-align: left; + + .icon { + color: #1f85de; + } +} + +.table th { + font-size: 0.85em; + letter-spacing: 0.085em; + text-transform: uppercase; +} diff --git a/ui/src/util/apiUrls.ts b/ui/src/util/apiUrls.ts index 4a9d51b..724d52b 100644 --- a/ui/src/util/apiUrls.ts +++ b/ui/src/util/apiUrls.ts @@ -1,3 +1,5 @@ +import {Configuration, DefaultApi} from "../sebrauc-client" + let apiHost = document.location.host const httpProto = document.location.protocol const wsProto = httpProto === "https:" ? "wss:" : "ws:" @@ -9,4 +11,10 @@ if (import.meta.env.VITE_API_HOST !== undefined) { const apiUrl = `${httpProto}//${apiHost}/api` const wsUrl = `${wsProto}//${apiHost}/api/ws` -export {apiUrl, wsUrl} +let apicfg = new Configuration({ + basePath: apiUrl, +}) + +const sebraucApi = new DefaultApi(apicfg) + +export {apiUrl, wsUrl, sebraucApi} diff --git a/ui/src/util/colors.ts b/ui/src/util/colors.ts new file mode 100644 index 0000000..3202366 --- /dev/null +++ b/ui/src/util/colors.ts @@ -0,0 +1,7 @@ +class colors { + static readonly RED = "#FF0039" + static readonly GREEN = "#148420" + static readonly BLUE = "#1f85de" +} + +export default colors diff --git a/ui/src/util/functions.ts b/ui/src/util/functions.ts new file mode 100644 index 0000000..18179e2 --- /dev/null +++ b/ui/src/util/functions.ts @@ -0,0 +1,18 @@ +function secondsToString(seconds: number): string { + const numyears = Math.floor(seconds / 31536000) + const numdays = Math.floor((seconds % 31536000) / 86400) + const numhours = Math.floor(((seconds % 31536000) % 86400) / 3600) + const numminutes = Math.floor((((seconds % 31536000) % 86400) % 3600) / 60) + const numseconds = (((seconds % 31536000) % 86400) % 3600) % 60 + + let res = [] + if (numyears > 0) res.push(numyears + "yr") + if (numdays > 0) res.push(numdays + "d") + if (numhours > 0) res.push(numhours + "h") + if (numminutes > 0) res.push(numminutes + "m") + if (seconds < 60) res.push(numseconds + "s") + + return res.join(" ") +} + +export {secondsToString} From 44001bb7e7708119d544c8f2ee094654ac23dcc2 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Mon, 6 Dec 2021 00:59:23 +0100 Subject: [PATCH 07/27] add hostname to sysinfo --- openapi.yml | 5 +++++ src/sysinfo/sysinfo.go | 17 +++++++++++++++-- ui/src/components/Updater/SysinfoCard.tsx | 2 +- ui/src/sebrauc-client/api.ts | 8 +++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/openapi.yml b/openapi.yml index d343569..fe288c0 100644 --- a/openapi.yml +++ b/openapi.yml @@ -118,6 +118,10 @@ components: SystemInfo: type: object properties: + hostname: + description: Hostname of the system + type: string + example: "raspberrypi3" os_name: description: Name of the os distribution type: string @@ -144,6 +148,7 @@ components: additionalProperties: $ref: "#/components/schemas/Rootfs" required: + - hostname - os_name - os_version - uptime diff --git a/src/sysinfo/sysinfo.go b/src/sysinfo/sysinfo.go index 57caafe..f00f389 100644 --- a/src/sysinfo/sysinfo.go +++ b/src/sysinfo/sysinfo.go @@ -5,11 +5,13 @@ import ( "os" "regexp" "strconv" + "strings" "code.thetadev.de/TSGRain/SEBRAUC/src/util" ) type SystemInfo struct { + Hostname string `json:"hostname"` OsName string `json:"os_name"` OsVersion string `json:"os_version"` Uptime int `json:"uptime"` @@ -118,10 +120,11 @@ func getFSNameFromBootname(rfslist map[string]Rootfs, bootname string) string { return "n/a" } -func mapSysinfo(rinf raucInfo, osr osRelease, uptime int) SystemInfo { +func mapSysinfo(rinf raucInfo, osr osRelease, uptime int, hostname string) SystemInfo { rfslist := mapRootfs(rinf) return SystemInfo{ + Hostname: hostname, OsName: osr.OsName, OsVersion: osr.OsVersion, Uptime: uptime, @@ -141,6 +144,14 @@ func getUptime() (int, error) { return strconv.Atoi(string(uptimeChars)) } +func getHostname() string { + hostname, err := os.ReadFile("/etc/hostname") + if err != nil { + return "" + } + return strings.TrimSpace(string(hostname)) +} + func GetSysinfo() (SystemInfo, error) { cmd := util.CommandFromString(util.RaucCmd + " status") rinfJson, err := cmd.Output() @@ -163,5 +174,7 @@ func GetSysinfo() (SystemInfo, error) { return SystemInfo{}, err } - return mapSysinfo(rinf, osinf, uptime), nil + hostname := getHostname() + + return mapSysinfo(rinf, osinf, uptime, hostname), nil } diff --git a/ui/src/components/Updater/SysinfoCard.tsx b/ui/src/components/Updater/SysinfoCard.tsx index 56813a3..04bd51c 100644 --- a/ui/src/components/Updater/SysinfoCard.tsx +++ b/ui/src/components/Updater/SysinfoCard.tsx @@ -56,7 +56,7 @@ export default class SysinfoCard extends Component { Hostname - TODO + {this.state.sysinfo.hostname} diff --git a/ui/src/sebrauc-client/api.ts b/ui/src/sebrauc-client/api.ts index 65eaf45..82abc3d 100644 --- a/ui/src/sebrauc-client/api.ts +++ b/ui/src/sebrauc-client/api.ts @@ -85,7 +85,7 @@ export interface Rootfs { * @type {string} * @memberof Rootfs */ - device?: string + device: string /** * Filesystem * @type {string} @@ -142,6 +142,12 @@ export interface StatusMessage { * @interface SystemInfo */ export interface SystemInfo { + /** + * Hostname of the system + * @type {string} + * @memberof SystemInfo + */ + hostname: string /** * Name of the os distribution * @type {string} From 0125f4e3febec6cbbc1ea76606e0118cc156b6e1 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Mon, 6 Dec 2021 01:32:03 +0100 Subject: [PATCH 08/27] fix rauc status command --- src/sysinfo/sysinfo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sysinfo/sysinfo.go b/src/sysinfo/sysinfo.go index f00f389..47cc936 100644 --- a/src/sysinfo/sysinfo.go +++ b/src/sysinfo/sysinfo.go @@ -153,7 +153,7 @@ func getHostname() string { } func GetSysinfo() (SystemInfo, error) { - cmd := util.CommandFromString(util.RaucCmd + " status") + cmd := util.CommandFromString(util.RaucCmd + " status --output-format=json") rinfJson, err := cmd.Output() if err != nil { return SystemInfo{}, err From 0428ad3ebc32de0fb0b74e33e595dac66ce6b858 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 12 Dec 2021 22:41:02 +0100 Subject: [PATCH 09/27] add central websocket client --- ui/src/components/Updater/UpdaterCard.tsx | 50 +++++------- ui/src/util/websocket.ts | 92 +++++++++++++++++++++++ 2 files changed, 112 insertions(+), 30 deletions(-) create mode 100644 ui/src/util/websocket.ts diff --git a/ui/src/components/Updater/UpdaterCard.tsx b/ui/src/components/Updater/UpdaterCard.tsx index 6873897..9a38b95 100644 --- a/ui/src/components/Updater/UpdaterCard.tsx +++ b/ui/src/components/Updater/UpdaterCard.tsx @@ -7,8 +7,9 @@ import Icon from "../Icon/Icon" import "./Updater.scss" import Alert from "./Alert" import Reboot from "./Reboot" -import {sebraucApi, wsUrl} from "../../util/apiUrls" +import {sebraucApi} from "../../util/apiUrls" import colors from "../../util/colors" +import WebsocketClient from "../../util/websocket" class UploadStatus { uploading = false @@ -52,19 +53,19 @@ type State = { export default class UpdaterCard extends Component { private dropzoneRef = createRef() - private conn: WebSocket | undefined + private ws: WebsocketClient constructor(props?: Props | undefined, context?: any) { super(props, context) + this.ws = new WebsocketClient(this.onWsStatusUpdate, this.onWsMessage) + this.state = { uploadStatus: new UploadStatus(false), uploadFilename: "", raucStatus: new RaucStatus(), - wsConnected: false, + wsConnected: this.ws.api().isConnected(), } - - this.connectWebsocket() } private buttonClick = () => { @@ -105,31 +106,16 @@ export default class UpdaterCard extends Component { this.dropzoneRef.current?.reset() } - private connectWebsocket = () => { - if (window.WebSocket) { - this.conn = new WebSocket(wsUrl) - this.conn.onopen = () => { - this.setState({wsConnected: true}) - console.log("WS connected") - } - this.conn.onclose = () => { - this.setState({wsConnected: false}) - console.log("WS connection closed") - window.setTimeout(this.connectWebsocket, 3000) - } - this.conn.onmessage = (evt) => { - var messages = evt.data.split("\n") - for (var i = 0; i < messages.length; i++) { - this.setState({ - raucStatus: Object.assign( - new RaucStatus(), - JSON.parse(messages[i]) - ), - }) - } - } - } else { - console.log("Your browser does not support WebSockets") + private onWsStatusUpdate = (wsConnected: boolean) => { + this.setState({wsConnected: wsConnected}) + } + + private onWsMessage = (evt: MessageEvent) => { + var messages = evt.data.split("\n") + for (var i = 0; i < messages.length; i++) { + this.setState({ + raucStatus: Object.assign(new RaucStatus(), JSON.parse(messages[i])), + }) } } @@ -167,6 +153,10 @@ export default class UpdaterCard extends Component { return 0 } + componentWillUnmount() { + this.ws.destroy() + } + render() { const acceptUploads = this.acceptUploads() const circleColor = this.circleColor() diff --git a/ui/src/util/websocket.ts b/ui/src/util/websocket.ts new file mode 100644 index 0000000..8d75673 --- /dev/null +++ b/ui/src/util/websocket.ts @@ -0,0 +1,92 @@ +import {wsUrl} from "./apiUrls" + +class WebsocketAPI { + private static ws: WebsocketAPI | undefined + + private conn: WebSocket | undefined + private wsConnected: boolean + + private clients: Set + + private constructor() { + this.clients = new Set() + this.wsConnected = false + + if (window.WebSocket) { + this.connect() + } else { + console.log("Your browser does not support WebSockets") + } + } + + private setStatus(wsConnected: boolean) { + if (wsConnected !== this.wsConnected) { + this.wsConnected = wsConnected + this.clients.forEach((client) => { + client.statusCallback(this.wsConnected) + }) + } + } + + private connect() { + this.conn = new WebSocket(wsUrl) + this.conn.onopen = () => { + this.setStatus(true) + console.log("WS connected") + } + this.conn.onclose = () => { + this.setStatus(false) + console.log("WS connection closed") + window.setTimeout(() => this.connect(), 3000) + } + this.conn.onmessage = (evt) => { + this.clients.forEach((client) => { + client.msgCallback(evt) + }) + } + } + + static Get(): WebsocketAPI { + if (this.ws === undefined) { + this.ws = new WebsocketAPI() + } + return this.ws + } + + isConnected(): boolean { + return this.wsConnected + } + + addClient(client: WebsocketClient) { + console.log("added client", client) + this.clients.add(client) + } + + removeClient(client: WebsocketClient) { + console.log("removed client", client) + this.clients.delete(client) + } +} + +export default class WebsocketClient { + statusCallback: (wsConnected: boolean) => void + msgCallback: (evt: MessageEvent) => void + + constructor( + statusCallback: (wsConnected: boolean) => void, + msgCallback: (evt: MessageEvent) => void + ) { + this.statusCallback = statusCallback + this.msgCallback = msgCallback + + this.api().addClient(this) + } + + api(): WebsocketAPI { + return WebsocketAPI.Get() + } + + destroy() { + this.api().removeClient(this) + } +} From c8e2d2a216945124a6e08b1ba716496a00ea1107 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 17 Dec 2021 19:14:59 +0100 Subject: [PATCH 10/27] migrated to gin-gonic --- .golangci.yaml | 6 +- go.mod | 10 +- go.sum | 97 +++++-- src/rauc/rauc.go | 22 +- src/server/hub.go | 98 ------- src/server/server.go | 129 ++++------ src/server/stream/client.go | 119 +++++++++ src/server/stream/hub.go | 1 + src/server/stream/hub_test.go | 1 + src/server/stream/once.go | 38 +++ src/server/stream/once_test.go | 43 ++++ src/server/stream/stream.go | 187 ++++++++++++++ src/server/stream/stream_test.go | 424 +++++++++++++++++++++++++++++++ src/util/counter.go | 30 +++ src/util/counter_test.go | 30 +++ ui/index.html | 4 + ui/src/components/app.tsx | 4 +- ui/src/util/config.ts | 23 ++ ui/src/util/version.ts | 7 - ui/ui.go | 58 ++++- 20 files changed, 1102 insertions(+), 229 deletions(-) delete mode 100644 src/server/hub.go create mode 100644 src/server/stream/client.go create mode 100644 src/server/stream/hub.go create mode 100644 src/server/stream/hub_test.go create mode 100644 src/server/stream/once.go create mode 100644 src/server/stream/once_test.go create mode 100644 src/server/stream/stream.go create mode 100644 src/server/stream/stream_test.go create mode 100644 src/util/counter.go create mode 100644 src/util/counter_test.go create mode 100644 ui/src/util/config.ts delete mode 100644 ui/src/util/version.ts diff --git a/.golangci.yaml b/.golangci.yaml index 3898f1c..31a4beb 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,7 +1,6 @@ linters: presets: - bugs - - unused - import - module @@ -14,6 +13,7 @@ linters: disable: - scopelint + - noctx linters-settings: lll: @@ -22,3 +22,7 @@ linters-settings: min-complexity: 10 nestif: min-complexity: 3 + errcheck: + exclude-functions: + - "(*github.com/gin-gonic/gin.Context).Error" + - "(*github.com/gin-gonic/gin.Context).AbortWithError" diff --git a/go.mod b/go.mod index 5b0a27c..36fe1e4 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,14 @@ module code.thetadev.de/TSGRain/SEBRAUC go 1.16 require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gofiber/fiber/v2 v2.21.0 - github.com/gofiber/websocket/v2 v2.0.12 + code.thetadev.de/TSGRain/ginzip v0.1.1 + github.com/fortytw2/leaktest v1.3.0 + github.com/gin-contrib/cors v1.3.1 + github.com/gin-gonic/gin v1.7.7 github.com/google/uuid v1.3.0 + github.com/gorilla/websocket v1.4.2 github.com/stretchr/testify v1.7.0 + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect + golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 4188720..c2354c4 100644 --- a/go.sum +++ b/go.sum @@ -1,49 +1,92 @@ -github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= -github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +code.thetadev.de/TSGRain/ginzip v0.1.1 h1:+X0L6qumEZiKYSLmM+Q0LqKVHsKvdcg4CVzsEpvM7fk= +code.thetadev.de/TSGRain/ginzip v0.1.1/go.mod h1:BH7VkvpP83vPRyMQ8rLIjKycQwGzF+/mFV0BKzg+BuA= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 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/fasthttp/websocket v1.4.3-rc.9 h1:CWJH0vONrOatdKXZgkgbFKWllijD9aY50C5KfbSDcWk= -github.com/fasthttp/websocket v1.4.3-rc.9/go.mod h1:eXL2zqDbexYJxaCw8/PQlm7VcMK6uoGvwbYbTdt4dFo= -github.com/gofiber/fiber/v2 v2.20.1/go.mod h1:/LdZHMUXZvTTo7gU4+b1hclqCAdoQphNQ9bi9gutPyI= -github.com/gofiber/fiber/v2 v2.21.0 h1:tdRNrgqWqcHWBwE3o51oAleEVsil4Ro02zd2vMEuP4Q= -github.com/gofiber/fiber/v2 v2.21.0/go.mod h1:MR1usVH3JHYRyQwMe2eZXRSZHRX38fkV+A7CPB+DlDQ= -github.com/gofiber/websocket/v2 v2.0.12 h1:jKwTrXiOut9UGOGEzFTAD6gq+/78mM3NcrI05VbxjAU= -github.com/gofiber/websocket/v2 v2.0.12/go.mod h1:lQRy0u5ACJfiez/e/bhGeYvM0/M940Y3NFw14U3/otI= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA= +github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s= -github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4 h1:ocK/D6lCgLji37Z2so4xhMl46se1ntReQQCUIU4BWI8= -github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.29.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= -github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= -github.com/valyala/fasthttp v1.31.0 h1:lrauRLII19afgCs2fnWRJ4M5IkV0lo2FqA61uGkNBfE= -github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= -github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/rauc/rauc.go b/src/rauc/rauc.go index 3e021fe..696cb25 100644 --- a/src/rauc/rauc.go +++ b/src/rauc/rauc.go @@ -18,7 +18,7 @@ var ( ) type Rauc struct { - broadcast chan string + bc broadcaster status RaucStatus runningMtx sync.Mutex } @@ -31,19 +31,23 @@ type RaucStatus struct { Log string `json:"log"` } -func NewRauc(broadcast chan string) *Rauc { +type broadcaster interface { + Broadcast(msg []byte) +} + +func NewRauc(bc broadcaster) *Rauc { r := &Rauc{ - broadcast: broadcast, + bc: bc, } - r.broadcast <- r.GetStatusJson() + r.bc.Broadcast(r.GetStatusJson()) return r } func (r *Rauc) completed(updateFile string) { r.status.Installing = false - r.broadcast <- r.GetStatusJson() + r.bc.Broadcast(r.GetStatusJson()) _ = os.Remove(updateFile) } @@ -68,7 +72,7 @@ func (r *Rauc) RunRauc(updateFile string) error { r.status = RaucStatus{ Installing: true, } - r.broadcast <- r.GetStatusJson() + r.bc.Broadcast(r.GetStatusJson()) cmd := util.CommandFromString(fmt.Sprintf("%s install %s", util.RaucCmd, updateFile)) @@ -100,7 +104,7 @@ func (r *Rauc) RunRauc(updateFile string) error { } if hasUpdate { - r.broadcast <- r.GetStatusJson() + r.bc.Broadcast(r.GetStatusJson()) } } }() @@ -126,7 +130,7 @@ func (r *Rauc) GetStatus() RaucStatus { return r.status } -func (r *Rauc) GetStatusJson() string { +func (r *Rauc) GetStatusJson() []byte { statusJson, _ := json.Marshal(r.status) - return string(statusJson) + return statusJson } diff --git a/src/server/hub.go b/src/server/hub.go deleted file mode 100644 index 77e1dfd..0000000 --- a/src/server/hub.go +++ /dev/null @@ -1,98 +0,0 @@ -package server - -import ( - "log" - "sync" - - "github.com/gofiber/websocket/v2" -) - -type hubClient struct{} - -type MessageHub struct { - Broadcast chan string - - clients map[*websocket.Conn]hubClient - register chan *websocket.Conn - unregister chan *websocket.Conn - lastMessage string - - running bool - runningMtx sync.Mutex -} - -func NewHub() *MessageHub { - return &MessageHub{ - clients: make(map[*websocket.Conn]hubClient), - register: make(chan *websocket.Conn), - Broadcast: make(chan string, 5), - unregister: make(chan *websocket.Conn), - } -} - -func (hub *MessageHub) sendMessage(conn *websocket.Conn, message string) { - if err := conn.WriteMessage( - websocket.TextMessage, []byte(message)); err != nil { - log.Println("write error:", err) - - _ = conn.WriteMessage(websocket.CloseMessage, []byte{}) - _ = conn.Close() - delete(hub.clients, conn) - } -} - -func (hub *MessageHub) Run() { - hub.runningMtx.Lock() - isRunning := hub.running - hub.running = true - hub.runningMtx.Unlock() - - if isRunning { - return - } - - for { - select { - case conn := <-hub.register: - hub.clients[conn] = hubClient{} - log.Println("connection registered") - - case message := <-hub.Broadcast: - log.Println("message received:", message) - hub.lastMessage = message - - // Send the message to all clients - for conn := range hub.clients { - hub.sendMessage(conn, message) - } - - case conn := <-hub.unregister: - // Remove the client from the hub - delete(hub.clients, conn) - - log.Println("connection unregistered") - } - } -} - -func (hub *MessageHub) Handler(conn *websocket.Conn) { - // When the function returns, unregister the client and close the connection - defer func() { - hub.unregister <- conn - conn.Close() - }() - - // Register the client - hub.register <- conn - - if hub.lastMessage != "" { - hub.sendMessage(conn, hub.lastMessage) - } - - for { - _, _, err := conn.ReadMessage() - if err != nil { - return // Calls the deferred function, i.e. closes the connection on error - } - } -} diff --git a/src/server/server.go b/src/server/server.go index 46522a3..a6f684a 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -3,27 +3,23 @@ package server import ( "errors" "fmt" - "net/http" "strings" "time" "code.thetadev.de/TSGRain/SEBRAUC/src/rauc" + "code.thetadev.de/TSGRain/SEBRAUC/src/server/stream" "code.thetadev.de/TSGRain/SEBRAUC/src/sysinfo" "code.thetadev.de/TSGRain/SEBRAUC/src/util" "code.thetadev.de/TSGRain/SEBRAUC/ui" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/compress" - "github.com/gofiber/fiber/v2/middleware/cors" - "github.com/gofiber/fiber/v2/middleware/filesystem" - "github.com/gofiber/fiber/v2/middleware/logger" - "github.com/gofiber/websocket/v2" + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" "github.com/google/uuid" ) type SEBRAUCServer struct { address string raucUpdater *rauc.Rauc - hub *MessageHub + streamer *stream.API tmpdir string } @@ -33,9 +29,9 @@ type statusMessage struct { } func NewServer(address string) *SEBRAUCServer { - hub := NewHub() + streamer := stream.New(10*time.Second, 1*time.Second, []string{}) - raucUpdater := rauc.NewRauc(hub.Broadcast) + raucUpdater := rauc.NewRauc(streamer) tmpdir, err := util.GetTmpdir() if err != nil { @@ -45,127 +41,100 @@ func NewServer(address string) *SEBRAUCServer { return &SEBRAUCServer{ address: address, raucUpdater: raucUpdater, - hub: hub, - tmpdir: tmpdir, + // hub: hub, + streamer: streamer, + tmpdir: tmpdir, } } func (srv *SEBRAUCServer) Run() error { - app := fiber.New(fiber.Config{ - AppName: "SEBRAUC", - BodyLimit: 1024 * 1024 * 1024, - ErrorHandler: errorHandler, - DisableStartupMessage: true, - }) + router := gin.Default() - app.Use(logger.New()) - - app.Use(compress.New(compress.Config{ - Next: func(c *fiber.Ctx) bool { - return strings.HasPrefix(c.Path(), "/api") - }, - })) - - // just for testing - app.Use("/api", cors.New()) - - app.Use("/api/ws", func(c *fiber.Ctx) error { - // IsWebSocketUpgrade returns true if the client - // requested upgrade to the WebSocket protocol. - if websocket.IsWebSocketUpgrade(c) { - c.Locals("allowed", true) - return c.Next() - } - return fiber.ErrUpgradeRequired - }) - - app.Use("/", filesystem.New(filesystem.Config{ - Root: http.FS(ui.Assets), - PathPrefix: ui.AssetsDir, - MaxAge: 7200, - })) + // only for testing + router.Use(cors.Default()) // ROUTES - app.Get("/api/ws", websocket.New(srv.hub.Handler)) - app.Post("/api/update", srv.controllerUpdate) - app.Get("/api/status", srv.controllerStatus) - app.Get("/api/info", srv.controllerInfo) - app.Post("/api/reboot", srv.controllerReboot) + router.GET("/api/ws", srv.streamer.Handle) + router.GET("/api/status", srv.controllerStatus) + router.GET("/api/info", srv.controllerInfo) - // Start messaging hub - go srv.hub.Run() + router.POST("/api/update", srv.controllerUpdate) + router.POST("/api/reboot", srv.controllerReboot) - return app.Listen(srv.address) + // router.StaticFS("/", ui.GetFS()) + ui.Register(router) + + return router.Run(srv.address) } -func (srv *SEBRAUCServer) controllerUpdate(c *fiber.Ctx) error { +func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) { file, err := c.FormFile("updateFile") if err != nil { - return err + c.Error(err) + return } uid, err := uuid.NewRandom() if err != nil { - return err + c.Error(err) + return } updateFile := fmt.Sprintf("%s/update_%s.raucb", srv.tmpdir, uid.String()) - err = c.SaveFile(file, updateFile) + err = c.SaveUploadedFile(file, updateFile) if err != nil { - return err + c.Error(err) + return } err = srv.raucUpdater.RunRauc(updateFile) if err == nil { writeStatus(c, true, "Update started") } else if errors.Is(err, util.ErrAlreadyRunning) { - return fiber.NewError(fiber.StatusConflict, "already running") + c.AbortWithError(409, errors.New("already running")) } else { - return err + c.Error(err) + return } - return nil } -func (srv *SEBRAUCServer) controllerStatus(c *fiber.Ctx) error { - c.Context().SetStatusCode(200) - _ = c.JSON(srv.raucUpdater.GetStatus()) - return nil +func (srv *SEBRAUCServer) controllerStatus(c *gin.Context) { + c.JSON(200, srv.raucUpdater.GetStatus()) } -func (srv *SEBRAUCServer) controllerInfo(c *fiber.Ctx) error { +func (srv *SEBRAUCServer) controllerInfo(c *gin.Context) { info, err := sysinfo.GetSysinfo() if err != nil { - return err + c.Error(err) + } else { + c.JSON(200, info) } - - c.Context().SetStatusCode(200) - _ = c.JSON(info) - return nil } -func (srv *SEBRAUCServer) controllerReboot(c *fiber.Ctx) error { +func (srv *SEBRAUCServer) controllerReboot(c *gin.Context) { go util.Reboot(5 * time.Second) writeStatus(c, true, "System is rebooting") - return nil } -func errorHandler(c *fiber.Ctx, err error) error { +func errorHandler(c *gin.Context, err error) error { // API error handling - if strings.HasPrefix(c.Path(), "/api") { + if strings.HasPrefix(c.FullPath(), "/api") { writeStatus(c, false, err.Error()) } return err } -func writeStatus(c *fiber.Ctx, success bool, msg string) { - _ = c.JSON(statusMessage{ +func writeStatus(c *gin.Context, success bool, msg string) { + status := 200 + + if !success { + status = 500 + } + + c.JSON(status, statusMessage{ Success: success, Msg: msg, }) - - if success { - c.Context().SetStatusCode(200) - } } diff --git a/src/server/stream/client.go b/src/server/stream/client.go new file mode 100644 index 0000000..c00249f --- /dev/null +++ b/src/server/stream/client.go @@ -0,0 +1,119 @@ +package stream + +import ( + "errors" + "fmt" + "time" + + "github.com/gorilla/websocket" +) + +const ( + writeWait = 2 * time.Second +) + +var ping = func(conn *websocket.Conn) error { + return conn.WriteMessage(websocket.PingMessage, nil) +} + +var writeBytes = func(conn *websocket.Conn, data []byte) error { + return conn.WriteMessage(websocket.TextMessage, data) +} + +type client struct { + conn *websocket.Conn + onClose func(*client) + write chan []byte + id uint + once once +} + +func newClient(conn *websocket.Conn, id uint, onClose func(*client)) *client { + return &client{ + conn: conn, + write: make(chan []byte, 1), + id: id, + onClose: onClose, + } +} + +// Close closes the connection. +func (c *client) Close() { + c.once.Do(func() { + c.conn.Close() + close(c.write) + }) +} + +// NotifyClose closes the connection and notifies that the connection was closed. +func (c *client) NotifyClose() { + c.once.Do(func() { + c.conn.Close() + close(c.write) + c.onClose(c) + }) +} + +// startWriteHandler starts listening on the client connection. +// As we do not need anything from the client, +// we ignore incoming messages. Leaves the loop on errors. +func (c *client) startReading(pongWait time.Duration) { + defer c.NotifyClose() + c.conn.SetReadLimit(64) + _ = c.conn.SetReadDeadline(time.Now().Add(pongWait)) + c.conn.SetPongHandler(func(appData string) error { + _ = c.conn.SetReadDeadline(time.Now().Add(pongWait)) + return nil + }) + for { + if _, _, err := c.conn.NextReader(); err != nil { + printWebSocketError("ReadError", err) + return + } + } +} + +// startWriteHandler starts the write loop. The method has the following tasks: +// * ping the client in the interval provided as parameter +// * write messages send by the channel to the client +// * on errors exit the loop. +func (c *client) startWriteHandler(pingPeriod time.Duration) { + pingTicker := time.NewTicker(pingPeriod) + defer func() { + c.NotifyClose() + pingTicker.Stop() + }() + + for { + select { + case message, ok := <-c.write: + if !ok { + return + } + + _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if err := writeBytes(c.conn, message); err != nil { + printWebSocketError("WriteError", err) + return + } + case <-pingTicker.C: + _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ping(c.conn); err != nil { + printWebSocketError("PingError", err) + return + } + } + } +} + +func printWebSocketError(prefix string, err error) { + var closeError *websocket.CloseError + ok := errors.As(err, &closeError) + + if ok && closeError != nil && (closeError.Code == 1000 || closeError.Code == 1001) { + // normal closure + return + } + + fmt.Println("WebSocket:", prefix, err) +} diff --git a/src/server/stream/hub.go b/src/server/stream/hub.go new file mode 100644 index 0000000..11541cc --- /dev/null +++ b/src/server/stream/hub.go @@ -0,0 +1 @@ +package stream diff --git a/src/server/stream/hub_test.go b/src/server/stream/hub_test.go new file mode 100644 index 0000000..11541cc --- /dev/null +++ b/src/server/stream/hub_test.go @@ -0,0 +1 @@ +package stream diff --git a/src/server/stream/once.go b/src/server/stream/once.go new file mode 100644 index 0000000..2df2523 --- /dev/null +++ b/src/server/stream/once.go @@ -0,0 +1,38 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stream + +import ( + "sync" + "sync/atomic" +) + +// Modified version of sync.Once +// (https://github.com/golang/go/blob/master/src/sync/once.go) +// This version unlocks the mutex early and therefore doesn't +// hold the lock while executing func f(). +type once struct { + m sync.Mutex + done uint32 +} + +func (o *once) Do(f func()) { + if atomic.LoadUint32(&o.done) == 1 { + return + } + if o.mayExecute() { + f() + } +} + +func (o *once) mayExecute() bool { + o.m.Lock() + defer o.m.Unlock() + if o.done == 0 { + atomic.StoreUint32(&o.done, 1) + return true + } + return false +} diff --git a/src/server/stream/once_test.go b/src/server/stream/once_test.go new file mode 100644 index 0000000..53ec08d --- /dev/null +++ b/src/server/stream/once_test.go @@ -0,0 +1,43 @@ +package stream + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func Test_Execute(t *testing.T) { + executeOnce := once{} + execution := make(chan struct{}) + fExecute := func() { + execution <- struct{}{} + } + go executeOnce.Do(fExecute) + go executeOnce.Do(fExecute) + + select { + case <-execution: + // expected + case <-time.After(100 * time.Millisecond): + t.Fatal("fExecute should be executed once") + } + + select { + case <-execution: + t.Fatal("should only execute once") + case <-time.After(100 * time.Millisecond): + // expected + } + + assert.False(t, executeOnce.mayExecute()) + + go executeOnce.Do(fExecute) + + select { + case <-execution: + t.Fatal("should only execute once") + case <-time.After(100 * time.Millisecond): + // expected + } +} diff --git a/src/server/stream/stream.go b/src/server/stream/stream.go new file mode 100644 index 0000000..6133eeb --- /dev/null +++ b/src/server/stream/stream.go @@ -0,0 +1,187 @@ +package stream + +import ( + "net/http" + "net/url" + "regexp" + "strings" + "sync" + "time" + + "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" +) + +// The API provides a handler for a WebSocket stream API. +type API struct { + clients map[uint]*client + lock sync.RWMutex + pingPeriod time.Duration + pongTimeout time.Duration + upgrader *websocket.Upgrader + counter *util.Counter +} + +// New creates a new instance of API. +// pingPeriod: is the interval, in which is server sends the a ping to the client. +// pongTimeout: is the duration after the connection will be terminated, +// when the client does not respond with the pong command. +func New(pingPeriod, pongTimeout time.Duration, allowedWebSocketOrigins []string) *API { + return &API{ + clients: make(map[uint]*client), + pingPeriod: pingPeriod, + pongTimeout: pingPeriod + pongTimeout, + upgrader: newUpgrader(allowedWebSocketOrigins), + counter: &util.Counter{}, + } +} + +// NotifyDeletedUser closes existing connections for the given user. +func (a *API) NotifyDeletedClient(userID uint) error { + a.lock.Lock() + defer a.lock.Unlock() + if client, ok := a.clients[userID]; ok { + client.Close() + delete(a.clients, userID) + } + return nil +} + +// Notify notifies the clients with the given userID that a new messages was created. +func (a *API) Notify(userID uint, msg []byte) { + a.lock.RLock() + defer a.lock.RUnlock() + if client, ok := a.clients[userID]; ok { + client.write <- msg + } +} + +func (a *API) Broadcast(msg []byte) { + a.lock.RLock() + defer a.lock.RUnlock() + for _, client := range a.clients { + client.write <- msg + } +} + +func (a *API) remove(remove *client) { + a.lock.Lock() + defer a.lock.Unlock() + delete(a.clients, remove.id) +} + +func (a *API) register(client *client) { + a.lock.Lock() + defer a.lock.Unlock() + a.clients[client.id] = client +} + +// Handle handles incoming requests. +// First it upgrades the protocol to the WebSocket protocol and then starts listening +// for read and writes. +// swagger:operation GET /stream message streamMessages +// +// Websocket, return newly created messages. +// +// --- +// schema: ws, wss +// produces: [application/json] +// security: [clientTokenHeader: [], clientTokenQuery: [], basicAuth: []] +// responses: +// 200: +// description: Ok +// schema: +// $ref: "#/definitions/Message" +// 400: +// description: Bad Request +// schema: +// $ref: "#/definitions/Error" +// 401: +// description: Unauthorized +// schema: +// $ref: "#/definitions/Error" +// 403: +// description: Forbidden +// schema: +// $ref: "#/definitions/Error" +// 500: +// description: Server Error +// schema: +// $ref: "#/definitions/Error" +func (a *API) Handle(ctx *gin.Context) { + conn, err := a.upgrader.Upgrade(ctx.Writer, ctx.Request, nil) + if err != nil { + ctx.Error(err) + return + } + + client := newClient(conn, a.counter.Increment(), a.remove) + a.register(client) + go client.startReading(a.pongTimeout) + go client.startWriteHandler(a.pingPeriod) +} + +// Close closes all client connections and stops answering new connections. +func (a *API) Close() { + a.lock.Lock() + defer a.lock.Unlock() + + for _, client := range a.clients { + client.Close() + } + for k := range a.clients { + delete(a.clients, k) + } +} + +func isAllowedOrigin(r *http.Request, allowedOrigins []*regexp.Regexp) bool { + origin := r.Header.Get("origin") + if origin == "" { + return true + } + + u, err := url.Parse(origin) + if err != nil { + return false + } + + if strings.EqualFold(u.Host, r.Host) { + return true + } + + for _, allowedOrigin := range allowedOrigins { + if allowedOrigin.Match([]byte(strings.ToLower(u.Hostname()))) { + return true + } + } + + return false +} + +func newUpgrader(allowedWebSocketOrigins []string) *websocket.Upgrader { + // compiledAllowedOrigins := compileAllowedWebSocketOrigins(allowedWebSocketOrigins) + return &websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + /* + TODO: implement this + if mode.IsDev() { + return true + } + return isAllowedOrigin(r, compiledAllowedOrigins) + */ + return true + }, + } +} + +func compileAllowedWebSocketOrigins(allowedOrigins []string) []*regexp.Regexp { + var compiledAllowedOrigins []*regexp.Regexp + for _, origin := range allowedOrigins { + compiledAllowedOrigins = append(compiledAllowedOrigins, regexp.MustCompile(origin)) + } + + return compiledAllowedOrigins +} diff --git a/src/server/stream/stream_test.go b/src/server/stream/stream_test.go new file mode 100644 index 0000000..fd779b1 --- /dev/null +++ b/src/server/stream/stream_test.go @@ -0,0 +1,424 @@ +package stream + +import ( + "errors" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/fortytw2/leaktest" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" +) + +func TestFailureOnNormalHttpRequest(t *testing.T) { + // mode.Set(mode.TestDev) + + defer leaktest.Check(t)() + + server, api := bootTestServer() + defer server.Close() + defer api.Close() + + resp, err := http.Get(server.URL) + assert.Nil(t, err) + assert.Equal(t, 400, resp.StatusCode) + resp.Body.Close() +} + +func TestWriteMessageFails(t *testing.T) { + // mode.Set(mode.TestDev) + oldWrite := writeBytes + // try emulate an write error, mostly this should kill the ReadMessage + // goroutine first but you'll never know. + writeBytes = func(conn *websocket.Conn, data []byte) error { + return errors.New("asd") + } + defer func() { + writeBytes = oldWrite + }() + defer leaktest.Check(t)() + + server, api := bootTestServer() + defer server.Close() + defer api.Close() + + wsURL := wsURL(server.URL) + user := testClient(t, wsURL) + + // the server may take some time to register the client + time.Sleep(100 * time.Millisecond) + client := getClient(api, 1) + assert.NotNil(t, client) + + api.Notify(1, []byte("HI")) + user.expectNoMessage() +} + +func TestWritePingFails(t *testing.T) { + // mode.Set(mode.TestDev) + oldPing := ping + // try emulate an write error, mostly this should kill the ReadMessage + // gorouting first but you'll never know. + ping = func(conn *websocket.Conn) error { + return errors.New("asd") + } + defer func() { + ping = oldPing + }() + + defer leaktest.CheckTimeout(t, 10*time.Second)() + + server, api := bootTestServer() + defer api.Close() + defer server.Close() + + wsURL := wsURL(server.URL) + user := testClient(t, wsURL) + defer user.conn.Close() + + // the server may take some time to register the client + time.Sleep(100 * time.Millisecond) + client := getClient(api, 1) + + assert.NotNil(t, client) + + time.Sleep(api.pingPeriod) // waiting for ping + + api.Notify(1, []byte("HI")) + user.expectNoMessage() +} + +func TestPing(t *testing.T) { + // mode.Set(mode.TestDev) + + server, api := bootTestServer() + defer server.Close() + defer api.Close() + + wsURL := wsURL(server.URL) + + user := createClient(t, wsURL) + defer user.conn.Close() + + ping := make(chan bool) + oldPingHandler := user.conn.PingHandler() + user.conn.SetPingHandler(func(appData string) error { + err := oldPingHandler(appData) + ping <- true + return err + }) + + startReading(user) + + expectNoMessage(user) + + select { + case <-time.After(2 * time.Second): + assert.Fail(t, "Expected ping but there was one :(") + case <-ping: + // expected + } + + expectNoMessage(user) + api.Notify(1, []byte("HI")) + user.expectMessage([]byte("HI")) +} + +func TestCloseClientOnNotReading(t *testing.T) { + // mode.Set(mode.TestDev) + + server, api := bootTestServer() + defer server.Close() + defer api.Close() + + wsURL := wsURL(server.URL) + + ws, resp, err := websocket.DefaultDialer.Dial(wsURL, nil) + assert.Nil(t, err) + resp.Body.Close() + defer ws.Close() + + // the server may take some time to register the client + time.Sleep(100 * time.Millisecond) + assert.NotNil(t, getClient(api, 1)) + + time.Sleep(api.pingPeriod + api.pongTimeout) + + assert.Nil(t, getClient(api, 1)) +} + +func TestMessageDirectlyAfterConnect(t *testing.T) { + // mode.Set(mode.Prod) + defer leaktest.Check(t)() + server, api := bootTestServer() + defer server.Close() + defer api.Close() + + wsURL := wsURL(server.URL) + + user := testClient(t, wsURL) + defer user.conn.Close() + // the server may take some time to register the client + time.Sleep(100 * time.Millisecond) + api.Notify(1, []byte("msg")) + user.expectMessage([]byte("msg")) +} + +func TestDeleteClientShouldCloseConnection(t *testing.T) { + // mode.Set(mode.Prod) + defer leaktest.Check(t)() + server, api := bootTestServer() + defer server.Close() + defer api.Close() + + wsURL := wsURL(server.URL) + + user := testClient(t, wsURL) + defer user.conn.Close() + // the server may take some time to register the client + time.Sleep(100 * time.Millisecond) + api.Notify(1, []byte("HI")) + user.expectMessage([]byte("HI")) + + assert.Nil(t, api.NotifyDeletedClient(1)) + + api.Notify(1, []byte("HI")) + user.expectNoMessage() +} + +func TestNotify(t *testing.T) { + // mode.Set(mode.TestDev) + + defer leaktest.Check(t)() + server, api := bootTestServer() + defer server.Close() + + wsURL := wsURL(server.URL) + + client1 := testClient(t, wsURL) + defer client1.conn.Close() + + client2 := testClient(t, wsURL) + defer client2.conn.Close() + + client3 := testClient(t, wsURL) + defer client3.conn.Close() + + // the server may take some time to register the client + time.Sleep(100 * time.Millisecond) + + api.Notify(1, []byte("msg")) + expectMessage([]byte("msg"), client1) + expectNoMessage(client2) + expectNoMessage(client3) + + assert.Nil(t, api.NotifyDeletedClient(1)) + + api.Notify(1, []byte("msg")) + expectNoMessage(client1) + expectNoMessage(client2) + expectNoMessage(client3) + + api.Notify(2, []byte("msg")) + expectNoMessage(client1) + expectMessage([]byte("msg"), client2) + expectNoMessage(client3) + + api.Notify(3, []byte("msg")) + expectNoMessage(client1) + expectNoMessage(client2) + expectMessage([]byte("msg"), client3) + + api.Close() +} + +func TestBroadcast(t *testing.T) { + defer leaktest.Check(t)() + server, api := bootTestServer() + defer server.Close() + + wsURL := wsURL(server.URL) + + client1 := testClient(t, wsURL) + defer client1.conn.Close() + + client2 := testClient(t, wsURL) + defer client2.conn.Close() + + client3 := testClient(t, wsURL) + defer client3.conn.Close() + + // the server may take some time to register the client + time.Sleep(100 * time.Millisecond) + + testMsg1 := []byte("hello1") + api.Broadcast(testMsg1) + expectMessage(testMsg1, client1, client2, client3) + + assert.Nil(t, api.NotifyDeletedClient(1)) + + testMsg2 := []byte("hello2") + api.Broadcast(testMsg2) + expectNoMessage(client1) + expectMessage(testMsg2, client2, client3) +} + +func Test_sameOrigin_returnsTrue(t *testing.T) { + // mode.Set(mode.Prod) + req := httptest.NewRequest("GET", "http://example.com/stream", nil) + req.Header.Set("Origin", "http://example.com") + actual := isAllowedOrigin(req, nil) + assert.True(t, actual) +} + +func Test_sameOrigin_returnsTrue_withCustomPort(t *testing.T) { + // mode.Set(mode.Prod) + req := httptest.NewRequest("GET", "http://example.com:8080/stream", nil) + req.Header.Set("Origin", "http://example.com:8080") + actual := isAllowedOrigin(req, nil) + assert.True(t, actual) +} + +func Test_isAllowedOrigin_withoutAllowedOrigins_failsWhenNotSameOrigin(t *testing.T) { + // mode.Set(mode.Prod) + req := httptest.NewRequest("GET", "http://example.com/stream", nil) + req.Header.Set("Origin", "http://gorify.example.com") + actual := isAllowedOrigin(req, nil) + assert.False(t, actual) +} + +func Test_isAllowedOriginMatching(t *testing.T) { + // mode.Set(mode.Prod) + compiledAllowedOrigins := compileAllowedWebSocketOrigins( + []string{"go.{4}\\.example\\.com", "go\\.example\\.com"}, + ) + + req := httptest.NewRequest("GET", "http://example.me/stream", nil) + req.Header.Set("Origin", "http://gorify.example.com") + assert.True(t, isAllowedOrigin(req, compiledAllowedOrigins)) + + req.Header.Set("Origin", "http://go.example.com") + assert.True(t, isAllowedOrigin(req, compiledAllowedOrigins)) + + req.Header.Set("Origin", "http://hello.example.com") + assert.False(t, isAllowedOrigin(req, compiledAllowedOrigins)) +} + +func Test_emptyOrigin_returnsTrue(t *testing.T) { + // mode.Set(mode.Prod) + req := httptest.NewRequest("GET", "http://example.com/stream", nil) + actual := isAllowedOrigin(req, nil) + assert.True(t, actual) +} + +func Test_otherOrigin_returnsFalse(t *testing.T) { + // mode.Set(mode.Prod) + req := httptest.NewRequest("GET", "http://example.com/stream", nil) + req.Header.Set("Origin", "http://otherexample.de") + actual := isAllowedOrigin(req, nil) + assert.False(t, actual) +} + +func Test_invalidOrigin_returnsFalse(t *testing.T) { + // mode.Set(mode.Prod) + req := httptest.NewRequest("GET", "http://example.com/stream", nil) + req.Header.Set("Origin", "http\\://otherexample.de") + actual := isAllowedOrigin(req, nil) + assert.False(t, actual) +} + +func Test_compileAllowedWebSocketOrigins(t *testing.T) { + assert.Equal(t, 0, len(compileAllowedWebSocketOrigins([]string{}))) + assert.Equal(t, 3, len(compileAllowedWebSocketOrigins([]string{"^.*$", "", "abc"}))) +} + +func getClient(api *API, user uint) *client { + api.lock.RLock() + defer api.lock.RUnlock() + + return api.clients[user] +} + +func testClient(t *testing.T, url string) *testingClient { + client := createClient(t, url) + startReading(client) + return client +} + +func startReading(client *testingClient) { + go func() { + for { + _, payload, err := client.conn.ReadMessage() + if err != nil { + return + } + + client.readMessage <- payload + } + }() +} + +func createClient(t *testing.T, url string) *testingClient { + ws, resp, err := websocket.DefaultDialer.Dial(url, nil) + assert.Nil(t, err) + resp.Body.Close() + + readMessages := make(chan []byte) + + return &testingClient{conn: ws, readMessage: readMessages, t: t} +} + +type testingClient struct { + conn *websocket.Conn + readMessage chan []byte + t *testing.T +} + +func (c *testingClient) expectMessage(expected []byte) { + select { + case <-time.After(50 * time.Millisecond): + assert.Fail(c.t, "Expected message but none was send :(") + case actual := <-c.readMessage: + assert.Equal(c.t, expected, actual) + } +} + +func expectMessage(expected []byte, clients ...*testingClient) { + for _, client := range clients { + client.expectMessage(expected) + } +} + +func expectNoMessage(clients ...*testingClient) { + for _, client := range clients { + client.expectNoMessage() + } +} + +func (c *testingClient) expectNoMessage() { + select { + case <-time.After(50 * time.Millisecond): + // no message == as expected + case msg := <-c.readMessage: + assert.Fail(c.t, "Expected NO message but there was one :(", fmt.Sprint(msg)) + } +} + +func bootTestServer() (*httptest.Server, *API) { + r := gin.New() + // ping every 500 ms, and the client has 500 ms to respond + api := New(500*time.Millisecond, 500*time.Millisecond, []string{}) + + r.GET("/", api.Handle) + server := httptest.NewServer(r) + return server, api +} + +func wsURL(httpURL string) string { + return "ws" + strings.TrimPrefix(httpURL, "http") +} diff --git a/src/util/counter.go b/src/util/counter.go new file mode 100644 index 0000000..9e264ee --- /dev/null +++ b/src/util/counter.go @@ -0,0 +1,30 @@ +package util + +import "sync" + +type Counter struct { + count uint + mutex sync.RWMutex +} + +func (c *Counter) Get() uint { + c.mutex.RLock() + defer c.mutex.RUnlock() + + return c.count +} + +func (c *Counter) Reset() { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.count = 0 +} + +func (c *Counter) Increment() uint { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.count++ + return c.count +} diff --git a/src/util/counter_test.go b/src/util/counter_test.go new file mode 100644 index 0000000..bfc3e9d --- /dev/null +++ b/src/util/counter_test.go @@ -0,0 +1,30 @@ +package util + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCounter(t *testing.T) { + counter := Counter{} + + var wg sync.WaitGroup + + incrementer := func() { + for i := 0; i < 50; i++ { + counter.Increment() + } + wg.Done() + } + + for i := 0; i < 100; i++ { + wg.Add(1) + go incrementer() + } + + wg.Wait() + + assert.EqualValues(t, 5000, counter.Get()) +} diff --git a/ui/index.html b/ui/index.html index cf876c6..426ad0f 100644 --- a/ui/index.html +++ b/ui/index.html @@ -7,7 +7,11 @@ SEBRAUC +
+ diff --git a/ui/src/components/app.tsx b/ui/src/components/app.tsx index b068bc3..c2bd70c 100644 --- a/ui/src/components/app.tsx +++ b/ui/src/components/app.tsx @@ -1,14 +1,14 @@ import {Component} from "preact" import UpdaterView from "./Updater/UpdaterView" import logo from "../assets/logo.svg" -import {version} from "../util/version" +import {getConfig} from "../util/config" export default class App extends Component { render() { return (
SEBRAUC - {version} + {getConfig().version}
) diff --git a/ui/src/util/config.ts b/ui/src/util/config.ts new file mode 100644 index 0000000..1e0e124 --- /dev/null +++ b/ui/src/util/config.ts @@ -0,0 +1,23 @@ +export interface Config { + version: string +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +declare global { + interface Window { + config?: any + } +} + +function isConfig(object: any): object is Config { + return typeof object === "object" && "version" in object +} + +export function getConfig(): Config { + if (isConfig(window.config)) { + return window.config + } + return { + version: "dev", + } +} diff --git a/ui/src/util/version.ts b/ui/src/util/version.ts deleted file mode 100644 index 9991968..0000000 --- a/ui/src/util/version.ts +++ /dev/null @@ -1,7 +0,0 @@ -let version = import.meta.env.VITE_VERSION - -if (version === undefined) { - version = "unknown" -} - -export {version} diff --git a/ui/ui.go b/ui/ui.go index 1d28f93..a10fda6 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -1,10 +1,64 @@ package ui import ( + "bytes" "embed" + "encoding/json" + "io/fs" + "net/http" + + "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "code.thetadev.de/TSGRain/ginzip" + "github.com/gin-gonic/gin" ) -const AssetsDir = "dist" +const distDir = "dist" //go:embed dist/** -var Assets embed.FS +var assets embed.FS + +type uiConfig struct { + Version string `json:"version"` +} + +func subFS(fsys fs.FS, dir string) fs.FS { + sub, err := fs.Sub(fsys, dir) + if err != nil { + panic(err) + } + return sub +} + +func distFS() fs.FS { + return subFS(assets, distDir) +} + +func Register(r *gin.Engine) { + indexHandler := getIndexHandler() + + ui := r.Group("/", ginzip.New(ginzip.DefaultOptions())) + + ui.GET("/", indexHandler) + ui.GET("/index.html", indexHandler) + + ui.StaticFS("/assets", http.FS(subFS(distFS(), "assets"))) +} + +func getIndexHandler() gin.HandlerFunc { + content, err := fs.ReadFile(distFS(), "index.html") + if err != nil { + panic(err) + } + + uiConfigBytes, err := json.Marshal(uiConfig{ + Version: util.Version(), + }) + if err != nil { + panic(err) + } + content = bytes.ReplaceAll(content, []byte("\"%CONFIG%\""), uiConfigBytes) + + return func(c *gin.Context) { + c.Data(200, "text/html", content) + } +} From 8714ad966b0a8beef1ece028db22a7501d017578 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 18 Dec 2021 00:05:57 +0100 Subject: [PATCH 11/27] auto-generate swagger documentation --- .pre-commit-config.yaml | 21 ++- Makefile | 10 +- openapi.yml | 204 ------------------------- src/model/rauc_status.go | 31 ++++ src/model/status_message.go | 13 ++ src/model/system_info.go | 71 +++++++++ src/rauc/rauc.go | 15 +- src/server/docs.go | 17 +++ src/server/mode/mode.go | 43 ++++++ src/server/mode/mode_test.go | 35 +++++ src/server/server.go | 100 ++++++++++-- src/server/stream/hub.go | 1 - src/server/stream/hub_test.go | 1 - src/server/stream/stream.go | 48 +----- src/server/stream/stream_test.go | 31 ++-- src/sysinfo/sysinfo.go | 48 ++---- src/sysinfo/sysinfo_test.go | 7 +- swagger.yaml | 201 ++++++++++++++++++++++++ .prettierignore => ui/.prettierignore | 2 + ui/package.json | 4 +- ui/src/sebrauc-client/api.ts | 158 +++++++++---------- ui/src/sebrauc-client/base.ts | 4 +- ui/src/sebrauc-client/common.ts | 2 +- ui/src/sebrauc-client/configuration.ts | 2 +- ui/src/sebrauc-client/index.ts | 2 +- 25 files changed, 658 insertions(+), 413 deletions(-) delete mode 100644 openapi.yml create mode 100644 src/model/rauc_status.go create mode 100644 src/model/status_message.go create mode 100644 src/model/system_info.go create mode 100644 src/server/docs.go create mode 100644 src/server/mode/mode.go create mode 100644 src/server/mode/mode_test.go delete mode 100644 src/server/stream/hub.go delete mode 100644 src/server/stream/hub_test.go create mode 100644 swagger.yaml rename .prettierignore => ui/.prettierignore (66%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 91ff3f8..c597da4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,17 +6,17 @@ repos: name: GolangCI Lint - id: go-test-repo-mod name: Backend tests - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.4.1 - hooks: - - id: prettier - - repo: https://github.com/dudefellah/pre-commit-openapi - rev: "v0.0.1" - hooks: - - id: check-openapi - repo: local hooks: + - id: check-swagger + name: check-swagger + language: system + files: swagger.yaml + entry: swagger + args: ["validate", "swagger.yaml"] + pass_filenames: false + - id: tsc name: tsc entry: tsc @@ -25,3 +25,8 @@ repos: args: ["-p", "./ui/tsconfig.json"] additional_dependencies: ["typescript@4.5.2"] pass_filenames: false + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.4.1 + hooks: + - id: prettier diff --git a/Makefile b/Makefile index c058ed0..36c51f6 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,10 @@ setup: test: go test -v ./src/... +lint: + golangci-lint run + cd ${UI_DIR} && npm run format && npm run lint + build-ui: cd ${UI_DIR} && VITE_VERSION=${VERSION} pnpm run build @@ -17,8 +21,12 @@ build-server: build: build-ui build-server +generate-apidoc: + SWAGGER_GENERATE_EXTENSION=false swagger generate spec --scan-models -o swagger.yaml + generate-apiclient: - openapi-generator generate -i openapi.yml -g typescript-axios -o ${UI_DIR}/src/sebrauc-client -p "supportsES6=true" + openapi-generator generate -i swagger.yaml -g typescript-axios -o ${UI_DIR}/src/sebrauc-client -p "supportsES6=true" + cd ${UI_DIR} && npm run format clean: rm -f build/* diff --git a/openapi.yml b/openapi.yml deleted file mode 100644 index fe288c0..0000000 --- a/openapi.yml +++ /dev/null @@ -1,204 +0,0 @@ -openapi: 3.0.3 -info: - title: SEBRAUC - version: 0.1.0 - description: REST API for the SEBRAUC firmware updater -servers: - - url: http://localhost:8080/api -paths: - /status: - get: - operationId: getStatus - responses: - 200: - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/RaucStatus" - default: - description: Server error - content: - application/json: - schema: - $ref: "#/components/schemas/StatusMessage" - - /info: - get: - operationId: getInfo - responses: - 200: - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/SystemInfo" - default: - description: Server error - content: - application/json: - schema: - $ref: "#/components/schemas/StatusMessage" - - /update: - post: - operationId: startUpdate - requestBody: - content: - multipart/form-data: - schema: - type: object - properties: - updateFile: - type: string - format: binary - responses: - 200: - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/StatusMessage" - default: - description: Server error - content: - application/json: - schema: - $ref: "#/components/schemas/StatusMessage" - - /reboot: - post: - operationId: startReboot - responses: - 200: - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/StatusMessage" - default: - description: Server error - content: - application/json: - schema: - $ref: "#/components/schemas/StatusMessage" - -components: - schemas: - RaucStatus: - type: object - properties: - installing: - description: True if the installer is running - type: boolean - percent: - description: Installation progress - type: integer - minimum: 0 - maximum: 100 - message: - description: Current installation step - type: string - example: Copying image to rootfs.0 - last_error: - description: Installation error message - type: string - example: "Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?" - log: - description: Full command line output of the current installation - type: string - example: "0% Installing\n0% Determining slot states\n20% Determining slot states done.\n" - required: - - installing - - percent - - message - - last_error - - log - - SystemInfo: - type: object - properties: - hostname: - description: Hostname of the system - type: string - example: "raspberrypi3" - os_name: - description: Name of the os distribution - type: string - example: "Poky" - os_version: - description: Operating system version - type: string - example: "1.0.2" - uptime: - description: System uptime in seconds - type: integer - example: 5832 - rauc_compatible: - description: Compatible firmware name - type: string - example: "Poky" - rauc_variant: - description: Compatible firmware variant - type: string - example: "rpi-prod" - rauc_rootfs: - description: List of RAUC root filesystems - type: object - additionalProperties: - $ref: "#/components/schemas/Rootfs" - required: - - hostname - - os_name - - os_version - - uptime - - rauc_compatible - - rauc_variant - - rauc_rootfs - - Rootfs: - type: object - properties: - device: - description: Block device - type: string - example: "/dev/mmcblk0p2" - type: - description: Filesystem - type: string - example: ext4 - mountpoint: - description: Mount path (null when not mounted) - type: string - nullable: true - example: "/" - bootable: - description: "Is the filesystem bootable" - type: boolean - booted: - description: "Is the filesystem booted" - type: boolean - primary: - description: "Is the filesystem the next boot target" - type: boolean - required: - - device - - type - - mountpoint - - bootable - - booted - - primary - - StatusMessage: - type: object - properties: - success: - description: Is operation successful - type: boolean - msg: - description: Success message - type: string - example: Update started - required: - - success - - msg diff --git a/src/model/rauc_status.go b/src/model/rauc_status.go new file mode 100644 index 0000000..52eb88b --- /dev/null +++ b/src/model/rauc_status.go @@ -0,0 +1,31 @@ +package model + +//swagger:model RaucStatus +type RaucStatus struct { + // True if the installer is running + // required: true + Installing bool `json:"installing"` + + // Installation progress + // required: true + // minimum: 0 + // maximum: 100 + Percent int `json:"percent"` + + // Current installation step + // required: true + // example: Copying image to rootfs.0 + Message string `json:"message"` + + // Installation error message + // required: true + //nolint:lll + // example: Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle? + LastError string `json:"last_error"` + + // Full command line output of the current installation + // required: true + //nolint:lll + // example: 0% Installing\n0% Determining slot states\n20% Determining slot states done\n + Log string `json:"log"` +} diff --git a/src/model/status_message.go b/src/model/status_message.go new file mode 100644 index 0000000..256b004 --- /dev/null +++ b/src/model/status_message.go @@ -0,0 +1,13 @@ +package model + +//swagger:model StatusMessage +type StatusMessage struct { + // Is operation successful? + // required: true + Success bool `json:"success"` + + // Status message text + // required: true + // example: Update started + Msg string `json:"msg"` +} diff --git a/src/model/system_info.go b/src/model/system_info.go new file mode 100644 index 0000000..ec02bbb --- /dev/null +++ b/src/model/system_info.go @@ -0,0 +1,71 @@ +package model + +//swagger:model SystemInfo +type SystemInfo struct { + // Hostname of the system + // required: true + // example: raspberrypi3 + Hostname string `json:"hostname"` + + // Name of the os distribution + // required: true + // example: Poky + OsName string `json:"os_name"` + + // Operating system version + // required: true + // example: 1.0.2 + OsVersion string `json:"os_version"` + + // System uptime in seconds + // required: true + // example: 5832 + Uptime int `json:"uptime"` + + // Compatible firmware name + // required: true + // example: Poky + RaucCompatible string `json:"rauc_compatible"` + + // Compatible firmware variant + // required: true + // example: rpi-prod + RaucVariant string `json:"rauc_variant"` + + // List of RAUC root filesystems + // required: true + RaucRootfs map[string]Rootfs `json:"rauc_rootfs"` +} + +//swagger:model Rootfs +type Rootfs struct { + // Block device + // required: true + // example: /dev/mmcblk0p2 + Device string `json:"device"` + + // Filesystem + // required: true + // example: ext4 + Type string `json:"type"` + + // Mount path (null when not mounted) + // required: true + // nullable: true + // example: / + Mountpoint *string `json:"mountpoint"` + + // Is the filesystem bootable? + // required: true + Bootable bool `json:"bootable"` + + // Is the filesystem booted? + // required: true + Booted bool `json:"booted"` + + // Is the filesystem the next boot target? + // required: true + Primary bool `json:"primary"` + + Bootname string `json:"-"` +} diff --git a/src/rauc/rauc.go b/src/rauc/rauc.go index 696cb25..5c1b4be 100644 --- a/src/rauc/rauc.go +++ b/src/rauc/rauc.go @@ -9,6 +9,7 @@ import ( "strconv" "sync" + "code.thetadev.de/TSGRain/SEBRAUC/src/model" "code.thetadev.de/TSGRain/SEBRAUC/src/util" ) @@ -19,18 +20,10 @@ var ( type Rauc struct { bc broadcaster - status RaucStatus + status model.RaucStatus runningMtx sync.Mutex } -type RaucStatus struct { - Installing bool `json:"installing"` - Percent int `json:"percent"` - Message string `json:"message"` - LastError string `json:"last_error"` - Log string `json:"log"` -} - type broadcaster interface { Broadcast(msg []byte) } @@ -69,7 +62,7 @@ func (r *Rauc) RunRauc(updateFile string) error { } // Reset installer - r.status = RaucStatus{ + r.status = model.RaucStatus{ Installing: true, } r.bc.Broadcast(r.GetStatusJson()) @@ -126,7 +119,7 @@ func (r *Rauc) RunRauc(updateFile string) error { return nil } -func (r *Rauc) GetStatus() RaucStatus { +func (r *Rauc) GetStatus() model.RaucStatus { return r.status } diff --git a/src/server/docs.go b/src/server/docs.go new file mode 100644 index 0000000..1704805 --- /dev/null +++ b/src/server/docs.go @@ -0,0 +1,17 @@ +// SEBRAUC +// +// REST API for the SEBRAUC firmware updater +// +// --- +// Schemes: http, https +// Version: 0.2.0 +// License: MIT +// +// Consumes: +// - application/json +// +// Produces: +// - application/json +// +// swagger:meta +package server diff --git a/src/server/mode/mode.go b/src/server/mode/mode.go new file mode 100644 index 0000000..59e297f --- /dev/null +++ b/src/server/mode/mode.go @@ -0,0 +1,43 @@ +package mode + +import "github.com/gin-gonic/gin" + +const ( + // Dev for development mode. + Dev = "dev" + // Prod for production mode. + Prod = "prod" + // TestDev used for tests. + TestDev = "testdev" +) + +var mode = Dev + +// Set sets the new mode. +func Set(newMode string) { + mode = newMode + updateGinMode() +} + +// Get returns the current mode. +func Get() string { + return mode +} + +// IsDev returns true if the current mode is dev mode. +func IsDev() bool { + return Get() == Dev || Get() == TestDev +} + +func updateGinMode() { + switch Get() { + case Dev: + gin.SetMode(gin.DebugMode) + case TestDev: + gin.SetMode(gin.TestMode) + case Prod: + gin.SetMode(gin.ReleaseMode) + default: + panic("unknown mode") + } +} diff --git a/src/server/mode/mode_test.go b/src/server/mode/mode_test.go new file mode 100644 index 0000000..8b06166 --- /dev/null +++ b/src/server/mode/mode_test.go @@ -0,0 +1,35 @@ +package mode + +import ( + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestDevMode(t *testing.T) { + Set(Dev) + assert.Equal(t, Get(), Dev) + assert.True(t, IsDev()) + assert.Equal(t, gin.Mode(), gin.DebugMode) +} + +func TestTestDevMode(t *testing.T) { + Set(TestDev) + assert.Equal(t, Get(), TestDev) + assert.True(t, IsDev()) + assert.Equal(t, gin.Mode(), gin.TestMode) +} + +func TestProdMode(t *testing.T) { + Set(Prod) + assert.Equal(t, Get(), Prod) + assert.False(t, IsDev()) + assert.Equal(t, gin.Mode(), gin.ReleaseMode) +} + +func TestInvalidMode(t *testing.T) { + assert.Panics(t, func() { + Set("asdasda") + }) +} diff --git a/src/server/server.go b/src/server/server.go index a6f684a..7452edd 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -6,7 +6,9 @@ import ( "strings" "time" + "code.thetadev.de/TSGRain/SEBRAUC/src/model" "code.thetadev.de/TSGRain/SEBRAUC/src/rauc" + "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" "code.thetadev.de/TSGRain/SEBRAUC/src/server/stream" "code.thetadev.de/TSGRain/SEBRAUC/src/sysinfo" "code.thetadev.de/TSGRain/SEBRAUC/src/util" @@ -23,11 +25,6 @@ type SEBRAUCServer struct { tmpdir string } -type statusMessage struct { - Success bool `json:"success"` - Msg string `json:"msg"` -} - func NewServer(address string) *SEBRAUCServer { streamer := stream.New(10*time.Second, 1*time.Second, []string{}) @@ -41,17 +38,17 @@ func NewServer(address string) *SEBRAUCServer { return &SEBRAUCServer{ address: address, raucUpdater: raucUpdater, - // hub: hub, - streamer: streamer, - tmpdir: tmpdir, + streamer: streamer, + tmpdir: tmpdir, } } func (srv *SEBRAUCServer) Run() error { router := gin.Default() - // only for testing - router.Use(cors.Default()) + if mode.IsDev() { + router.Use(cors.Default()) + } // ROUTES router.GET("/api/ws", srv.streamer.Handle) @@ -67,7 +64,40 @@ func (srv *SEBRAUCServer) Run() error { return router.Run(srv.address) } +// @Description Start the update process +// @Route /update +// +// _Param {name} {in} {goType} {required} {description} +// @Param updateFile form file true "Rauc firmware image file (*.raucb)" +// +// _Success {status} {jsonType} {goType} {description} +// @Success 200 object statusMessage +// @Failure 500 object statusMessage "Server error" func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) { + // swagger:operation POST /update startUpdate + // + // Start the update process + // + // --- + // consumes: + // - multipart/form-data + // produces: [application/json] + // parameters: + // - name: updateFile + // in: formData + // description: Rauc firmware image file (*.raucb) + // required: true + // type: file + // responses: + // 200: + // description: Ok + // schema: + // $ref: "#/definitions/StatusMessage" + // 500: + // description: Server Error + // schema: + // $ref: "#/definitions/StatusMessage" + file, err := c.FormFile("updateFile") if err != nil { c.Error(err) @@ -100,10 +130,42 @@ func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) { } func (srv *SEBRAUCServer) controllerStatus(c *gin.Context) { + // swagger:operation GET /status getStatus + // + // Get the current status of the RAUC updater + // + // --- + // produces: [application/json] + // responses: + // 200: + // description: Ok + // schema: + // $ref: "#/definitions/RaucStatus" + // 500: + // description: Server Error + // schema: + // $ref: "#/definitions/StatusMessage" + c.JSON(200, srv.raucUpdater.GetStatus()) } func (srv *SEBRAUCServer) controllerInfo(c *gin.Context) { + // swagger:operation GET /info getInfo + // + // Get the current system info + // + // --- + // produces: [application/json] + // responses: + // 200: + // description: Ok + // schema: + // $ref: "#/definitions/SystemInfo" + // 500: + // description: Server Error + // schema: + // $ref: "#/definitions/StatusMessage" + info, err := sysinfo.GetSysinfo() if err != nil { c.Error(err) @@ -113,6 +175,22 @@ func (srv *SEBRAUCServer) controllerInfo(c *gin.Context) { } func (srv *SEBRAUCServer) controllerReboot(c *gin.Context) { + // swagger:operation GET /reboot startReboot + // + // Reboot the system + // + // --- + // produces: [application/json] + // responses: + // 200: + // description: Ok + // schema: + // $ref: "#/definitions/StatusMessage" + // 500: + // description: Server Error + // schema: + // $ref: "#/definitions/StatusMessage" + go util.Reboot(5 * time.Second) writeStatus(c, true, "System is rebooting") @@ -133,7 +211,7 @@ func writeStatus(c *gin.Context, success bool, msg string) { status = 500 } - c.JSON(status, statusMessage{ + c.JSON(status, model.StatusMessage{ Success: success, Msg: msg, }) diff --git a/src/server/stream/hub.go b/src/server/stream/hub.go deleted file mode 100644 index 11541cc..0000000 --- a/src/server/stream/hub.go +++ /dev/null @@ -1 +0,0 @@ -package stream diff --git a/src/server/stream/hub_test.go b/src/server/stream/hub_test.go deleted file mode 100644 index 11541cc..0000000 --- a/src/server/stream/hub_test.go +++ /dev/null @@ -1 +0,0 @@ -package stream diff --git a/src/server/stream/stream.go b/src/server/stream/stream.go index 6133eeb..a60c624 100644 --- a/src/server/stream/stream.go +++ b/src/server/stream/stream.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" "code.thetadev.de/TSGRain/SEBRAUC/src/util" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" @@ -77,38 +78,6 @@ func (a *API) register(client *client) { a.clients[client.id] = client } -// Handle handles incoming requests. -// First it upgrades the protocol to the WebSocket protocol and then starts listening -// for read and writes. -// swagger:operation GET /stream message streamMessages -// -// Websocket, return newly created messages. -// -// --- -// schema: ws, wss -// produces: [application/json] -// security: [clientTokenHeader: [], clientTokenQuery: [], basicAuth: []] -// responses: -// 200: -// description: Ok -// schema: -// $ref: "#/definitions/Message" -// 400: -// description: Bad Request -// schema: -// $ref: "#/definitions/Error" -// 401: -// description: Unauthorized -// schema: -// $ref: "#/definitions/Error" -// 403: -// description: Forbidden -// schema: -// $ref: "#/definitions/Error" -// 500: -// description: Server Error -// schema: -// $ref: "#/definitions/Error" func (a *API) Handle(ctx *gin.Context) { conn, err := a.upgrader.Upgrade(ctx.Writer, ctx.Request, nil) if err != nil { @@ -160,19 +129,16 @@ func isAllowedOrigin(r *http.Request, allowedOrigins []*regexp.Regexp) bool { } func newUpgrader(allowedWebSocketOrigins []string) *websocket.Upgrader { - // compiledAllowedOrigins := compileAllowedWebSocketOrigins(allowedWebSocketOrigins) + compiledAllowedOrigins := compileAllowedWebSocketOrigins(allowedWebSocketOrigins) + return &websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { - /* - TODO: implement this - if mode.IsDev() { - return true - } - return isAllowedOrigin(r, compiledAllowedOrigins) - */ - return true + if mode.IsDev() { + return true + } + return isAllowedOrigin(r, compiledAllowedOrigins) }, } } diff --git a/src/server/stream/stream_test.go b/src/server/stream/stream_test.go index fd779b1..69f6dc2 100644 --- a/src/server/stream/stream_test.go +++ b/src/server/stream/stream_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" "github.com/fortytw2/leaktest" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" @@ -16,7 +17,7 @@ import ( ) func TestFailureOnNormalHttpRequest(t *testing.T) { - // mode.Set(mode.TestDev) + mode.Set(mode.TestDev) defer leaktest.Check(t)() @@ -31,7 +32,7 @@ func TestFailureOnNormalHttpRequest(t *testing.T) { } func TestWriteMessageFails(t *testing.T) { - // mode.Set(mode.TestDev) + mode.Set(mode.TestDev) oldWrite := writeBytes // try emulate an write error, mostly this should kill the ReadMessage // goroutine first but you'll never know. @@ -60,7 +61,7 @@ func TestWriteMessageFails(t *testing.T) { } func TestWritePingFails(t *testing.T) { - // mode.Set(mode.TestDev) + mode.Set(mode.TestDev) oldPing := ping // try emulate an write error, mostly this should kill the ReadMessage // gorouting first but you'll never know. @@ -94,7 +95,7 @@ func TestWritePingFails(t *testing.T) { } func TestPing(t *testing.T) { - // mode.Set(mode.TestDev) + mode.Set(mode.TestDev) server, api := bootTestServer() defer server.Close() @@ -130,7 +131,7 @@ func TestPing(t *testing.T) { } func TestCloseClientOnNotReading(t *testing.T) { - // mode.Set(mode.TestDev) + mode.Set(mode.TestDev) server, api := bootTestServer() defer server.Close() @@ -153,7 +154,7 @@ func TestCloseClientOnNotReading(t *testing.T) { } func TestMessageDirectlyAfterConnect(t *testing.T) { - // mode.Set(mode.Prod) + mode.Set(mode.Prod) defer leaktest.Check(t)() server, api := bootTestServer() defer server.Close() @@ -170,7 +171,7 @@ func TestMessageDirectlyAfterConnect(t *testing.T) { } func TestDeleteClientShouldCloseConnection(t *testing.T) { - // mode.Set(mode.Prod) + mode.Set(mode.Prod) defer leaktest.Check(t)() server, api := bootTestServer() defer server.Close() @@ -192,7 +193,7 @@ func TestDeleteClientShouldCloseConnection(t *testing.T) { } func TestNotify(t *testing.T) { - // mode.Set(mode.TestDev) + mode.Set(mode.TestDev) defer leaktest.Check(t)() server, api := bootTestServer() @@ -269,7 +270,7 @@ func TestBroadcast(t *testing.T) { } func Test_sameOrigin_returnsTrue(t *testing.T) { - // mode.Set(mode.Prod) + mode.Set(mode.Prod) req := httptest.NewRequest("GET", "http://example.com/stream", nil) req.Header.Set("Origin", "http://example.com") actual := isAllowedOrigin(req, nil) @@ -277,7 +278,7 @@ func Test_sameOrigin_returnsTrue(t *testing.T) { } func Test_sameOrigin_returnsTrue_withCustomPort(t *testing.T) { - // mode.Set(mode.Prod) + mode.Set(mode.Prod) req := httptest.NewRequest("GET", "http://example.com:8080/stream", nil) req.Header.Set("Origin", "http://example.com:8080") actual := isAllowedOrigin(req, nil) @@ -285,7 +286,7 @@ func Test_sameOrigin_returnsTrue_withCustomPort(t *testing.T) { } func Test_isAllowedOrigin_withoutAllowedOrigins_failsWhenNotSameOrigin(t *testing.T) { - // mode.Set(mode.Prod) + mode.Set(mode.Prod) req := httptest.NewRequest("GET", "http://example.com/stream", nil) req.Header.Set("Origin", "http://gorify.example.com") actual := isAllowedOrigin(req, nil) @@ -293,7 +294,7 @@ func Test_isAllowedOrigin_withoutAllowedOrigins_failsWhenNotSameOrigin(t *testin } func Test_isAllowedOriginMatching(t *testing.T) { - // mode.Set(mode.Prod) + mode.Set(mode.Prod) compiledAllowedOrigins := compileAllowedWebSocketOrigins( []string{"go.{4}\\.example\\.com", "go\\.example\\.com"}, ) @@ -310,14 +311,14 @@ func Test_isAllowedOriginMatching(t *testing.T) { } func Test_emptyOrigin_returnsTrue(t *testing.T) { - // mode.Set(mode.Prod) + mode.Set(mode.Prod) req := httptest.NewRequest("GET", "http://example.com/stream", nil) actual := isAllowedOrigin(req, nil) assert.True(t, actual) } func Test_otherOrigin_returnsFalse(t *testing.T) { - // mode.Set(mode.Prod) + mode.Set(mode.Prod) req := httptest.NewRequest("GET", "http://example.com/stream", nil) req.Header.Set("Origin", "http://otherexample.de") actual := isAllowedOrigin(req, nil) @@ -325,7 +326,7 @@ func Test_otherOrigin_returnsFalse(t *testing.T) { } func Test_invalidOrigin_returnsFalse(t *testing.T) { - // mode.Set(mode.Prod) + mode.Set(mode.Prod) req := httptest.NewRequest("GET", "http://example.com/stream", nil) req.Header.Set("Origin", "http\\://otherexample.de") actual := isAllowedOrigin(req, nil) diff --git a/src/sysinfo/sysinfo.go b/src/sysinfo/sysinfo.go index 47cc936..9442977 100644 --- a/src/sysinfo/sysinfo.go +++ b/src/sysinfo/sysinfo.go @@ -7,29 +7,10 @@ import ( "strconv" "strings" + "code.thetadev.de/TSGRain/SEBRAUC/src/model" "code.thetadev.de/TSGRain/SEBRAUC/src/util" ) -type SystemInfo struct { - Hostname string `json:"hostname"` - OsName string `json:"os_name"` - OsVersion string `json:"os_version"` - Uptime int `json:"uptime"` - RaucCompatible string `json:"rauc_compatible"` - RaucVariant string `json:"rauc_variant"` - RaucRootfs map[string]Rootfs `json:"rauc_rootfs"` -} - -type Rootfs struct { - Device string `json:"device"` - Type string `json:"type"` - bootname string - Mountpoint *string `json:"mountpoint"` - Bootable bool `json:"bootable"` - Booted bool `json:"booted"` - Primary bool `json:"primary"` -} - type raucInfo struct { Compatible string `json:"compatible"` Variant string `json:"variant"` @@ -90,16 +71,16 @@ func parseOsRelease(osReleaseFile string) (osRelease, error) { }, nil } -func mapRootfs(rinf raucInfo) map[string]Rootfs { - res := make(map[string]Rootfs) +func mapRootfs(rinf raucInfo) map[string]model.Rootfs { + res := make(map[string]model.Rootfs) for _, slot := range rinf.Slots { for name, fs := range slot { if fs.Class == "rootfs" { - res[name] = Rootfs{ + res[name] = model.Rootfs{ Device: fs.Device, Type: fs.Type, - bootname: fs.Bootname, + Bootname: fs.Bootname, Mountpoint: fs.Mountpoint, Bootable: fs.BootStatus == "good", Booted: fs.State == "booted", @@ -111,19 +92,20 @@ func mapRootfs(rinf raucInfo) map[string]Rootfs { return res } -func getFSNameFromBootname(rfslist map[string]Rootfs, bootname string) string { +func getFSNameFromBootname(rfslist map[string]model.Rootfs, bootname string) string { for name, rfs := range rfslist { - if rfs.bootname == bootname { + if rfs.Bootname == bootname { return name } } return "n/a" } -func mapSysinfo(rinf raucInfo, osr osRelease, uptime int, hostname string) SystemInfo { +func mapSysinfo(rinf raucInfo, osr osRelease, uptime int, + hostname string) model.SystemInfo { rfslist := mapRootfs(rinf) - return SystemInfo{ + return model.SystemInfo{ Hostname: hostname, OsName: osr.OsName, OsVersion: osr.OsVersion, @@ -152,26 +134,26 @@ func getHostname() string { return strings.TrimSpace(string(hostname)) } -func GetSysinfo() (SystemInfo, error) { +func GetSysinfo() (model.SystemInfo, error) { cmd := util.CommandFromString(util.RaucCmd + " status --output-format=json") rinfJson, err := cmd.Output() if err != nil { - return SystemInfo{}, err + return model.SystemInfo{}, err } rinf, err := parseRaucInfo(rinfJson) if err != nil { - return SystemInfo{}, err + return model.SystemInfo{}, err } osinf, err := parseOsRelease("/etc/os-release") if err != nil { - return SystemInfo{}, err + return model.SystemInfo{}, err } uptime, err := getUptime() if err != nil { - return SystemInfo{}, err + return model.SystemInfo{}, err } hostname := getHostname() diff --git a/src/sysinfo/sysinfo_test.go b/src/sysinfo/sysinfo_test.go index 3628435..1e6e86b 100644 --- a/src/sysinfo/sysinfo_test.go +++ b/src/sysinfo/sysinfo_test.go @@ -5,6 +5,7 @@ import ( "testing" "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures" + "code.thetadev.de/TSGRain/SEBRAUC/src/model" "github.com/stretchr/testify/assert" ) @@ -48,11 +49,11 @@ var expectedRaucInfo = raucInfo{ }, } -var expectedRootfsList = map[string]Rootfs{ +var expectedRootfsList = map[string]model.Rootfs{ "rootfs.0": { Device: "/dev/mmcblk0p2", Type: "ext4", - bootname: "A", + Bootname: "A", Mountpoint: &mountRoot, Bootable: true, Booted: true, @@ -61,7 +62,7 @@ var expectedRootfsList = map[string]Rootfs{ "rootfs.1": { Device: "/dev/mmcblk0p3", Type: "ext4", - bootname: "B", + Bootname: "B", Mountpoint: nil, Bootable: true, Booted: false, diff --git a/swagger.yaml b/swagger.yaml new file mode 100644 index 0000000..14afa86 --- /dev/null +++ b/swagger.yaml @@ -0,0 +1,201 @@ +consumes: + - application/json +definitions: + RaucStatus: + properties: + installing: + description: True if the installer is running + type: boolean + last_error: + description: Installation error message + example: "Failed to check bundle identifier: Invalid identifier. Did you pass + a valid RAUC bundle?" + type: string + log: + description: Full command line output of the current installation + example: 0% Installing\n0% Determining slot states\n20% Determining slot states + done\n + type: string + message: + description: Current installation step + example: Copying image to rootfs.0 + type: string + percent: + description: Installation progress + format: int64 + maximum: 100 + minimum: 0 + type: integer + required: + - installing + - percent + - message + - last_error + - log + type: object + Rootfs: + properties: + bootable: + description: Is the filesystem bootable? + type: boolean + booted: + description: Is the filesystem booted? + type: boolean + device: + description: Block device + example: /dev/mmcblk0p2 + type: string + mountpoint: + description: Mount path (null when not mounted) + example: / + type: string + primary: + description: Is the filesystem the next boot target? + type: boolean + type: + description: Filesystem + example: ext4 + type: string + required: + - device + - type + - mountpoint + - bootable + - booted + - primary + type: object + StatusMessage: + properties: + msg: + description: Status message text + example: Update started + type: string + success: + description: Is operation successful? + type: boolean + required: + - success + - msg + type: object + SystemInfo: + properties: + hostname: + description: Hostname of the system + example: raspberrypi3 + type: string + os_name: + description: Name of the os distribution + example: Poky + type: string + os_version: + description: Operating system version + example: 1.0.2 + type: string + rauc_compatible: + description: Compatible firmware name + example: Poky + type: string + rauc_rootfs: + additionalProperties: + $ref: "#/definitions/Rootfs" + description: List of RAUC root filesystems + type: object + rauc_variant: + description: Compatible firmware variant + example: rpi-prod + type: string + uptime: + description: System uptime in seconds + example: 5832 + format: int64 + type: integer + required: + - hostname + - os_name + - os_version + - uptime + - rauc_compatible + - rauc_variant + - rauc_rootfs + type: object +info: + description: REST API for the SEBRAUC firmware updater + license: + name: MIT + title: SEBRAUC + version: 0.2.0 +paths: + /info: + get: + description: Get the current system info + operationId: getInfo + produces: + - application/json + responses: + "200": + description: Ok + schema: + $ref: "#/definitions/SystemInfo" + "500": + description: Server Error + schema: + $ref: "#/definitions/StatusMessage" + /reboot: + get: + description: Reboot the system + operationId: startReboot + produces: + - application/json + responses: + "200": + description: Ok + schema: + $ref: "#/definitions/StatusMessage" + "500": + description: Server Error + schema: + $ref: "#/definitions/StatusMessage" + /status: + get: + description: Get the current status of the RAUC updater + operationId: getStatus + produces: + - application/json + responses: + "200": + description: Ok + schema: + $ref: "#/definitions/RaucStatus" + "500": + description: Server Error + schema: + $ref: "#/definitions/StatusMessage" + /update: + post: + consumes: + - multipart/form-data + description: Start the update process + operationId: startUpdate + parameters: + - description: Rauc firmware image file (*.raucb) + in: formData + name: updateFile + required: true + type: file + produces: + - application/json + responses: + "200": + description: Ok + schema: + $ref: "#/definitions/StatusMessage" + "500": + description: Server Error + schema: + $ref: "#/definitions/StatusMessage" +produces: + - application/json +schemes: + - http + - https +swagger: "2.0" diff --git a/.prettierignore b/ui/.prettierignore similarity index 66% rename from .prettierignore rename to ui/.prettierignore index f06235c..d638edf 100644 --- a/.prettierignore +++ b/ui/.prettierignore @@ -1,2 +1,4 @@ node_modules dist +tmp +.tmp diff --git a/ui/package.json b/ui/package.json index 5efdbac..1d1def8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -4,7 +4,9 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "serve": "vite preview" + "serve": "vite preview", + "lint": "tsc", + "format": "prettier --write ../" }, "dependencies": { "@mdi/js": "^6.5.95", diff --git a/ui/src/sebrauc-client/api.ts b/ui/src/sebrauc-client/api.ts index 82abc3d..015ad91 100644 --- a/ui/src/sebrauc-client/api.ts +++ b/ui/src/sebrauc-client/api.ts @@ -4,7 +4,7 @@ * SEBRAUC * REST API for the SEBRAUC firmware updater * - * The version of the OpenAPI document: 0.1.0 + * The version of the OpenAPI document: 0.2.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -49,18 +49,6 @@ export interface RaucStatus { * @memberof RaucStatus */ installing: boolean - /** - * Installation progress - * @type {number} - * @memberof RaucStatus - */ - percent: number - /** - * Current installation step - * @type {string} - * @memberof RaucStatus - */ - message: string /** * Installation error message * @type {string} @@ -73,6 +61,18 @@ export interface RaucStatus { * @memberof RaucStatus */ log: string + /** + * Current installation step + * @type {string} + * @memberof RaucStatus + */ + message: string + /** + * Installation progress + * @type {number} + * @memberof RaucStatus + */ + percent: number } /** * @@ -80,42 +80,42 @@ export interface RaucStatus { * @interface Rootfs */ export interface Rootfs { + /** + * Is the filesystem bootable? + * @type {boolean} + * @memberof Rootfs + */ + bootable: boolean + /** + * Is the filesystem booted? + * @type {boolean} + * @memberof Rootfs + */ + booted: boolean /** * Block device * @type {string} * @memberof Rootfs */ device: string + /** + * Mount path (null when not mounted) + * @type {string} + * @memberof Rootfs + */ + mountpoint: string + /** + * Is the filesystem the next boot target? + * @type {boolean} + * @memberof Rootfs + */ + primary: boolean /** * Filesystem * @type {string} * @memberof Rootfs */ type: string - /** - * Mount path (null when not mounted) - * @type {string} - * @memberof Rootfs - */ - mountpoint: string | null - /** - * Is the filesystem bootable - * @type {boolean} - * @memberof Rootfs - */ - bootable: boolean - /** - * Is the filesystem booted - * @type {boolean} - * @memberof Rootfs - */ - booted: boolean - /** - * Is the filesystem the next boot target - * @type {boolean} - * @memberof Rootfs - */ - primary: boolean } /** * @@ -124,17 +124,17 @@ export interface Rootfs { */ export interface StatusMessage { /** - * Is operation successful - * @type {boolean} - * @memberof StatusMessage - */ - success: boolean - /** - * Success message + * Status message text * @type {string} * @memberof StatusMessage */ msg: string + /** + * Is operation successful? + * @type {boolean} + * @memberof StatusMessage + */ + success: boolean } /** * @@ -160,18 +160,18 @@ export interface SystemInfo { * @memberof SystemInfo */ os_version: string - /** - * System uptime in seconds - * @type {number} - * @memberof SystemInfo - */ - uptime: number /** * Compatible firmware name * @type {string} * @memberof SystemInfo */ rauc_compatible: string + /** + * List of RAUC root filesystems + * @type {{ [key: string]: Rootfs; }} + * @memberof SystemInfo + */ + rauc_rootfs: {[key: string]: Rootfs} /** * Compatible firmware variant * @type {string} @@ -179,11 +179,11 @@ export interface SystemInfo { */ rauc_variant: string /** - * List of RAUC root filesystems - * @type {{ [key: string]: Rootfs; }} + * System uptime in seconds + * @type {number} * @memberof SystemInfo */ - rauc_rootfs: {[key: string]: Rootfs} + uptime: number } /** @@ -193,7 +193,7 @@ export interface SystemInfo { export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { return { /** - * + * Get the current system info * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -225,7 +225,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati } }, /** - * + * Get the current status of the RAUC updater * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -257,7 +257,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati } }, /** - * + * Reboot the system * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -270,7 +270,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati baseOptions = configuration.baseOptions } - const localVarRequestOptions = {method: "POST", ...baseOptions, ...options} + const localVarRequestOptions = {method: "GET", ...baseOptions, ...options} const localVarHeaderParameter = {} as any const localVarQueryParameter = {} as any @@ -289,15 +289,17 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati } }, /** - * - * @param {any} [updateFile] + * Start the update process + * @param {any} updateFile Rauc firmware image file (*.raucb) * @param {*} [options] Override http request option. * @throws {RequiredError} */ startUpdate: async ( - updateFile?: any, + updateFile: any, options: AxiosRequestConfig = {} ): Promise => { + // verify required parameter 'updateFile' is not null or undefined + assertParamExists("startUpdate", "updateFile", updateFile) const localVarPath = `/update` // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL) @@ -345,7 +347,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) return { /** - * + * Get the current system info * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -363,7 +365,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { ) }, /** - * + * Get the current status of the RAUC updater * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -381,7 +383,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { ) }, /** - * + * Reboot the system * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -401,13 +403,13 @@ export const DefaultApiFp = function (configuration?: Configuration) { ) }, /** - * - * @param {any} [updateFile] + * Start the update process + * @param {any} updateFile Rauc firmware image file (*.raucb) * @param {*} [options] Override http request option. * @throws {RequiredError} */ async startUpdate( - updateFile?: any, + updateFile: any, options?: AxiosRequestConfig ): Promise< (axios?: AxiosInstance, basePath?: string) => AxiosPromise @@ -438,7 +440,7 @@ export const DefaultApiFactory = function ( const localVarFp = DefaultApiFp(configuration) return { /** - * + * Get the current system info * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -448,7 +450,7 @@ export const DefaultApiFactory = function ( .then((request) => request(axios, basePath)) }, /** - * + * Get the current status of the RAUC updater * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -458,7 +460,7 @@ export const DefaultApiFactory = function ( .then((request) => request(axios, basePath)) }, /** - * + * Reboot the system * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -468,12 +470,12 @@ export const DefaultApiFactory = function ( .then((request) => request(axios, basePath)) }, /** - * - * @param {any} [updateFile] + * Start the update process + * @param {any} updateFile Rauc firmware image file (*.raucb) * @param {*} [options] Override http request option. * @throws {RequiredError} */ - startUpdate(updateFile?: any, options?: any): AxiosPromise { + startUpdate(updateFile: any, options?: any): AxiosPromise { return localVarFp .startUpdate(updateFile, options) .then((request) => request(axios, basePath)) @@ -489,7 +491,7 @@ export const DefaultApiFactory = function ( */ export class DefaultApi extends BaseAPI { /** - * + * Get the current system info * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApi @@ -501,7 +503,7 @@ export class DefaultApi extends BaseAPI { } /** - * + * Get the current status of the RAUC updater * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApi @@ -513,7 +515,7 @@ export class DefaultApi extends BaseAPI { } /** - * + * Reboot the system * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApi @@ -525,13 +527,13 @@ export class DefaultApi extends BaseAPI { } /** - * - * @param {any} [updateFile] + * Start the update process + * @param {any} updateFile Rauc firmware image file (*.raucb) * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApi */ - public startUpdate(updateFile?: any, options?: AxiosRequestConfig) { + public startUpdate(updateFile: any, options?: AxiosRequestConfig) { return DefaultApiFp(this.configuration) .startUpdate(updateFile, options) .then((request) => request(this.axios, this.basePath)) diff --git a/ui/src/sebrauc-client/base.ts b/ui/src/sebrauc-client/base.ts index 6248680..cf13305 100644 --- a/ui/src/sebrauc-client/base.ts +++ b/ui/src/sebrauc-client/base.ts @@ -4,7 +4,7 @@ * SEBRAUC * REST API for the SEBRAUC firmware updater * - * The version of the OpenAPI document: 0.1.0 + * The version of the OpenAPI document: 0.2.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -17,7 +17,7 @@ import {Configuration} from "./configuration" // @ts-ignore import globalAxios, {AxiosPromise, AxiosInstance, AxiosRequestConfig} from "axios" -export const BASE_PATH = "http://localhost:8080/api".replace(/\/+$/, "") +export const BASE_PATH = "http://localhost".replace(/\/+$/, "") /** * diff --git a/ui/src/sebrauc-client/common.ts b/ui/src/sebrauc-client/common.ts index 9942bb6..c0ce169 100644 --- a/ui/src/sebrauc-client/common.ts +++ b/ui/src/sebrauc-client/common.ts @@ -4,7 +4,7 @@ * SEBRAUC * REST API for the SEBRAUC firmware updater * - * The version of the OpenAPI document: 0.1.0 + * The version of the OpenAPI document: 0.2.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/ui/src/sebrauc-client/configuration.ts b/ui/src/sebrauc-client/configuration.ts index 357fd9b..5e30cfa 100644 --- a/ui/src/sebrauc-client/configuration.ts +++ b/ui/src/sebrauc-client/configuration.ts @@ -4,7 +4,7 @@ * SEBRAUC * REST API for the SEBRAUC firmware updater * - * The version of the OpenAPI document: 0.1.0 + * The version of the OpenAPI document: 0.2.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/ui/src/sebrauc-client/index.ts b/ui/src/sebrauc-client/index.ts index a0c08f6..3b3f852 100644 --- a/ui/src/sebrauc-client/index.ts +++ b/ui/src/sebrauc-client/index.ts @@ -4,7 +4,7 @@ * SEBRAUC * REST API for the SEBRAUC firmware updater * - * The version of the OpenAPI document: 0.1.0 + * The version of the OpenAPI document: 0.2.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). From e1c4c58684841219e92c38f47c76b7fa2d5032d3 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 18 Dec 2021 00:37:52 +0100 Subject: [PATCH 12/27] add swagger page --- .pre-commit-config.yaml | 8 ------- Makefile | 6 +++-- src/model/rauc_status.go | 6 ++--- src/server/server.go | 4 +++- src/server/{ => swagger}/docs.go | 8 +------ src/server/swagger/swagger.go | 24 +++++++++++++++++++ src/server/swagger/swagger.html | 20 ++++++++++++++++ .../server/swagger/swagger.yaml | 11 +++------ 8 files changed, 57 insertions(+), 30 deletions(-) rename src/server/{ => swagger}/docs.go (61%) create mode 100644 src/server/swagger/swagger.go create mode 100644 src/server/swagger/swagger.html rename swagger.yaml => src/server/swagger/swagger.yaml (95%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c597da4..cc4ae18 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,14 +9,6 @@ repos: - repo: local hooks: - - id: check-swagger - name: check-swagger - language: system - files: swagger.yaml - entry: swagger - args: ["validate", "swagger.yaml"] - pass_filenames: false - - id: tsc name: tsc entry: tsc diff --git a/Makefile b/Makefile index 36c51f6..2a1cbee 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ SRC_DIR=./src UI_DIR=./ui +APIDOC_FILE=./src/server/swagger/swagger.yaml + VERSION=$(shell git tag --sort=-version:refname | head -n 1) setup: @@ -22,10 +24,10 @@ build-server: build: build-ui build-server generate-apidoc: - SWAGGER_GENERATE_EXTENSION=false swagger generate spec --scan-models -o swagger.yaml + SWAGGER_GENERATE_EXTENSION=false swagger generate spec --scan-models -o ${APIDOC_FILE} generate-apiclient: - openapi-generator generate -i swagger.yaml -g typescript-axios -o ${UI_DIR}/src/sebrauc-client -p "supportsES6=true" + openapi-generator generate -i ${APIDOC_FILE} -g typescript-axios -o ${UI_DIR}/src/sebrauc-client -p "supportsES6=true" cd ${UI_DIR} && npm run format clean: diff --git a/src/model/rauc_status.go b/src/model/rauc_status.go index 52eb88b..d71d64d 100644 --- a/src/model/rauc_status.go +++ b/src/model/rauc_status.go @@ -19,13 +19,11 @@ type RaucStatus struct { // Installation error message // required: true - //nolint:lll - // example: Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle? + // example: Failed to check bundle identifier: Invalid identifier. LastError string `json:"last_error"` // Full command line output of the current installation // required: true - //nolint:lll - // example: 0% Installing\n0% Determining slot states\n20% Determining slot states done\n + // example: 0% Installing 0% Determining slot states 20% Determining slot states done Log string `json:"log"` } diff --git a/src/server/server.go b/src/server/server.go index 7452edd..3f91ecd 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -10,6 +10,7 @@ import ( "code.thetadev.de/TSGRain/SEBRAUC/src/rauc" "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" "code.thetadev.de/TSGRain/SEBRAUC/src/server/stream" + "code.thetadev.de/TSGRain/SEBRAUC/src/server/swagger" "code.thetadev.de/TSGRain/SEBRAUC/src/sysinfo" "code.thetadev.de/TSGRain/SEBRAUC/src/util" "code.thetadev.de/TSGRain/SEBRAUC/ui" @@ -58,9 +59,10 @@ func (srv *SEBRAUCServer) Run() error { router.POST("/api/update", srv.controllerUpdate) router.POST("/api/reboot", srv.controllerReboot) - // router.StaticFS("/", ui.GetFS()) ui.Register(router) + swagger.Register(router) + return router.Run(srv.address) } diff --git a/src/server/docs.go b/src/server/swagger/docs.go similarity index 61% rename from src/server/docs.go rename to src/server/swagger/docs.go index 1704805..198db73 100644 --- a/src/server/docs.go +++ b/src/server/swagger/docs.go @@ -7,11 +7,5 @@ // Version: 0.2.0 // License: MIT // -// Consumes: -// - application/json -// -// Produces: -// - application/json -// // swagger:meta -package server +package swagger diff --git a/src/server/swagger/swagger.go b/src/server/swagger/swagger.go new file mode 100644 index 0000000..732f009 --- /dev/null +++ b/src/server/swagger/swagger.go @@ -0,0 +1,24 @@ +package swagger + +import ( + _ "embed" + + "github.com/gin-gonic/gin" +) + +//go:embed swagger.html +var swaggerHtml []byte + +//go:embed swagger.yaml +var swaggerYaml []byte + +func Register(r *gin.Engine) { + swg := r.Group("/api/swagger") + + swg.GET("/", func(c *gin.Context) { + c.Data(200, "text/html", swaggerHtml) + }) + swg.GET("/swagger.yaml", func(c *gin.Context) { + c.Data(200, "text/yaml", swaggerYaml) + }) +} diff --git a/src/server/swagger/swagger.html b/src/server/swagger/swagger.html new file mode 100644 index 0000000..cc6855f --- /dev/null +++ b/src/server/swagger/swagger.html @@ -0,0 +1,20 @@ + + + + SEBRAUC API documentation + + + + + + + + + + + diff --git a/swagger.yaml b/src/server/swagger/swagger.yaml similarity index 95% rename from swagger.yaml rename to src/server/swagger/swagger.yaml index 14afa86..24989f5 100644 --- a/swagger.yaml +++ b/src/server/swagger/swagger.yaml @@ -1,5 +1,3 @@ -consumes: - - application/json definitions: RaucStatus: properties: @@ -8,13 +6,12 @@ definitions: type: boolean last_error: description: Installation error message - example: "Failed to check bundle identifier: Invalid identifier. Did you pass - a valid RAUC bundle?" + example: "Failed to check bundle identifier: Invalid identifier." type: string log: description: Full command line output of the current installation - example: 0% Installing\n0% Determining slot states\n20% Determining slot states - done\n + example: 0% Installing 0% Determining slot states 20% Determining slot states + done type: string message: description: Current installation step @@ -193,8 +190,6 @@ paths: description: Server Error schema: $ref: "#/definitions/StatusMessage" -produces: - - application/json schemes: - http - https From 71764dd6fa43541b946dbd662878a780064e0185 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 19 Dec 2021 17:01:15 +0100 Subject: [PATCH 13/27] add caching/error handling middleware --- go.mod | 2 + go.sum | 4 + src/assets/assets.go | 8 - src/assets/files/index.html | 88 --------- src/main.go | 12 +- src/model/error.go | 24 +++ src/model/rauc_status.go | 4 + src/model/status_message.go | 4 + src/model/system_info.go | 4 + src/rauc/rauc.go | 15 +- src/server/middleware/cache.go | 7 + src/server/middleware/error_handler.go | 58 ++++++ src/server/server.go | 260 ++++++++++++++----------- src/server/swagger/docs.go | 11 -- src/server/swagger/swagger.go | 3 +- src/server/swagger/swagger.yaml | 40 +++- src/util/commands.go | 4 +- src/util/commands_mock.go | 4 +- src/util/errors.go | 6 +- src/util/http_error.go | 39 ++++ src/util/types.go | 5 + ui/src/sebrauc-client/api.ts | 39 +++- ui/ui.go | 3 +- 23 files changed, 388 insertions(+), 256 deletions(-) delete mode 100644 src/assets/assets.go delete mode 100644 src/assets/files/index.html create mode 100644 src/model/error.go create mode 100644 src/server/middleware/cache.go create mode 100644 src/server/middleware/error_handler.go delete mode 100644 src/server/swagger/docs.go create mode 100644 src/util/http_error.go create mode 100644 src/util/types.go diff --git a/go.mod b/go.mod index 36fe1e4..2090ee2 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,11 @@ go 1.16 require ( code.thetadev.de/TSGRain/ginzip v0.1.1 + github.com/ekyoung/gin-nice-recovery v0.0.0-20160510022553-1654dca486db github.com/fortytw2/leaktest v1.3.0 github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.7.7 + github.com/go-errors/errors v1.4.1 // indirect github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.2 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index c2354c4..7011440 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG 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/ekyoung/gin-nice-recovery v0.0.0-20160510022553-1654dca486db h1:oZ4U9IqO8NS+61OmGTBi8vopzqTRxwQeogyBHdrhjbc= +github.com/ekyoung/gin-nice-recovery v0.0.0-20160510022553-1654dca486db/go.mod h1:Pk7/9x6tyChFTkahDvLBQMlvdsWvfC+yU8HTT5VD314= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA= @@ -14,6 +16,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= +github.com/go-errors/errors v1.4.1 h1:IvVlgbzSsaUNudsw5dcXSzF3EWyXTi5XrAdngnuhRyg= +github.com/go-errors/errors v1.4.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= diff --git a/src/assets/assets.go b/src/assets/assets.go deleted file mode 100644 index 8f2c9de..0000000 --- a/src/assets/assets.go +++ /dev/null @@ -1,8 +0,0 @@ -package assets - -import ( - "embed" -) - -//go:embed files/** -var Assets embed.FS diff --git a/src/assets/files/index.html b/src/assets/files/index.html deleted file mode 100644 index 0554e60..0000000 --- a/src/assets/files/index.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - Chat Example - - - - -
- -
- - -
- - diff --git a/src/main.go b/src/main.go index 2e83122..0bbf0f0 100644 --- a/src/main.go +++ b/src/main.go @@ -3,20 +3,28 @@ package main import ( "fmt" "log" + "time" + "code.thetadev.de/TSGRain/SEBRAUC/src/rauc" "code.thetadev.de/TSGRain/SEBRAUC/src/server" + "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" + "code.thetadev.de/TSGRain/SEBRAUC/src/server/stream" "code.thetadev.de/TSGRain/SEBRAUC/src/util" ) func main() { fmt.Println("SEBRAUC " + util.Version()) + mode.Set(util.Mode) - if util.TestMode { + if mode.IsDev() { fmt.Println("Test mode active - no update operations are executed.") fmt.Println("Build with -tags prod to enable live mode.") } - srv := server.NewServer(":8080") + streamer := stream.New(10*time.Second, 1*time.Second, []string{}) + raucUpdater := &rauc.Rauc{} + + srv := server.NewServer(":8080", streamer, raucUpdater) err := srv.Run() if err != nil { log.Fatalln(err) diff --git a/src/model/error.go b/src/model/error.go new file mode 100644 index 0000000..b2f1396 --- /dev/null +++ b/src/model/error.go @@ -0,0 +1,24 @@ +package model + +// Error model +// +// The Error contains error relevant information. +// +//swagger:model Error +type Error struct { + // The general error message according to HTTP specification. + // + // required: true + // example: Unauthorized + Error string `json:"error"` + // The http error code. + // + // required: true + // example: 500 + StatusCode int `json:"status_code"` + // Concrete error message. + // + // required: true + // example: already running + Message string `json:"msg"` +} diff --git a/src/model/rauc_status.go b/src/model/rauc_status.go index d71d64d..3a48b53 100644 --- a/src/model/rauc_status.go +++ b/src/model/rauc_status.go @@ -1,5 +1,9 @@ package model +// RaucStatus model +// +// RaucStatus contains information about the current RAUC updater status. +// //swagger:model RaucStatus type RaucStatus struct { // True if the installer is running diff --git a/src/model/status_message.go b/src/model/status_message.go index 256b004..d01fe03 100644 --- a/src/model/status_message.go +++ b/src/model/status_message.go @@ -1,5 +1,9 @@ package model +// StatusMessage model +// +// StatusMessage contains the status of an operation. +// //swagger:model StatusMessage type StatusMessage struct { // Is operation successful? diff --git a/src/model/system_info.go b/src/model/system_info.go index ec02bbb..6c923d7 100644 --- a/src/model/system_info.go +++ b/src/model/system_info.go @@ -1,5 +1,9 @@ package model +// SystemInfo model +// +// SystemInfo contains information about the running system. +// //swagger:model SystemInfo type SystemInfo struct { // Hostname of the system diff --git a/src/rauc/rauc.go b/src/rauc/rauc.go index 5c1b4be..e84b142 100644 --- a/src/rauc/rauc.go +++ b/src/rauc/rauc.go @@ -19,23 +19,14 @@ var ( ) type Rauc struct { - bc broadcaster + bc util.Broadcaster status model.RaucStatus runningMtx sync.Mutex } -type broadcaster interface { - Broadcast(msg []byte) -} - -func NewRauc(bc broadcaster) *Rauc { - r := &Rauc{ - bc: bc, - } - +func (r *Rauc) SetBroadcaster(bc util.Broadcaster) { + r.bc = bc r.bc.Broadcast(r.GetStatusJson()) - - return r } func (r *Rauc) completed(updateFile string) { diff --git a/src/server/middleware/cache.go b/src/server/middleware/cache.go new file mode 100644 index 0000000..8c97b26 --- /dev/null +++ b/src/server/middleware/cache.go @@ -0,0 +1,7 @@ +package middleware + +import "github.com/gin-gonic/gin" + +func Cache(c *gin.Context) { + c.Writer.Header().Set("Cache-Control", "public, max-age=604800, immutable") +} diff --git a/src/server/middleware/error_handler.go b/src/server/middleware/error_handler.go new file mode 100644 index 0000000..e774c66 --- /dev/null +++ b/src/server/middleware/error_handler.go @@ -0,0 +1,58 @@ +package middleware + +import ( + "errors" + "fmt" + "net/http" + + "code.thetadev.de/TSGRain/SEBRAUC/src/model" + "code.thetadev.de/TSGRain/SEBRAUC/src/util" + nice "github.com/ekyoung/gin-nice-recovery" + "github.com/gin-gonic/gin" +) + +// ErrorHandler creates a gin middleware for handling errors. +func ErrorHandler(isApi bool) gin.HandlerFunc { + return func(c *gin.Context) { + c.Next() + + if len(c.Errors) > 0 { + for _, e := range c.Errors { + writeError(c, e.Err, isApi) + } + } + } +} + +func PanicHandler(isApi bool) gin.HandlerFunc { + return nice.Recovery(func(c *gin.Context, err interface{}) { + writeError(c, fmt.Errorf("panic: %s", err), isApi) + }) +} + +func writeError(c *gin.Context, err error, isApi bool) { + status := http.StatusInternalServerError + + var httpErr util.HttpError + if errors.As(err, &httpErr) { + status = httpErr.StatusCode() + } + + // only write error message if there is no content + if c.Writer.Size() != -1 { + c.Status(status) + return + } + + if isApi { + // Machine-readable JSON error message + c.JSON(status, &model.Error{ + Error: http.StatusText(status), + StatusCode: status, + Message: err.Error(), + }) + } else { + // Human-readable error message + c.String(status, "%d %s: %s", status, http.StatusText(status), err.Error()) + } +} diff --git a/src/server/server.go b/src/server/server.go index 3f91ecd..3f1eafb 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -1,15 +1,24 @@ +// SEBRAUC +// +// REST API for the SEBRAUC firmware updater +// +// --- +// Schemes: http, https +// Version: 0.2.0 +// License: MIT +// +// swagger:meta package server import ( "errors" "fmt" - "strings" + "net/http" "time" "code.thetadev.de/TSGRain/SEBRAUC/src/model" - "code.thetadev.de/TSGRain/SEBRAUC/src/rauc" + "code.thetadev.de/TSGRain/SEBRAUC/src/server/middleware" "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" - "code.thetadev.de/TSGRain/SEBRAUC/src/server/stream" "code.thetadev.de/TSGRain/SEBRAUC/src/server/swagger" "code.thetadev.de/TSGRain/SEBRAUC/src/sysinfo" "code.thetadev.de/TSGRain/SEBRAUC/src/util" @@ -19,17 +28,28 @@ import ( "github.com/google/uuid" ) -type SEBRAUCServer struct { - address string - raucUpdater *rauc.Rauc - streamer *stream.API - tmpdir string +type serverStreamer interface { + util.Broadcaster + Handle(ctx *gin.Context) + Close() } -func NewServer(address string) *SEBRAUCServer { - streamer := stream.New(10*time.Second, 1*time.Second, []string{}) +type serverUpdater interface { + GetStatus() model.RaucStatus + RunRauc(updateFile string) error + SetBroadcaster(bc util.Broadcaster) +} - raucUpdater := rauc.NewRauc(streamer) +type SEBRAUCServer struct { + address string + streamer serverStreamer + udater serverUpdater + tmpdir string +} + +func NewServer(address string, streamer serverStreamer, + updater serverUpdater) *SEBRAUCServer { + updater.SetBroadcaster(streamer) tmpdir, err := util.GetTmpdir() if err != nil { @@ -37,27 +57,44 @@ func NewServer(address string) *SEBRAUCServer { } return &SEBRAUCServer{ - address: address, - raucUpdater: raucUpdater, - streamer: streamer, - tmpdir: tmpdir, + address: address, + udater: updater, + streamer: streamer, + tmpdir: tmpdir, } } func (srv *SEBRAUCServer) Run() error { - router := gin.Default() + router := gin.New() + router.Use(gin.Logger()) + _ = router.SetTrustedProxies(nil) if mode.IsDev() { router.Use(cors.Default()) } - // ROUTES - router.GET("/api/ws", srv.streamer.Handle) - router.GET("/api/status", srv.controllerStatus) - router.GET("/api/info", srv.controllerInfo) + router.Use(middleware.ErrorHandler(false), middleware.PanicHandler(false)) + router.NoRoute(func(c *gin.Context) { c.Error(util.ErrPageNotFound) }) - router.POST("/api/update", srv.controllerUpdate) - router.POST("/api/reboot", srv.controllerReboot) + api := router.Group("/api", + middleware.ErrorHandler(true), middleware.PanicHandler(true)) + + // ROUTES + api.GET("/ws", srv.streamer.Handle) + api.GET("/status", srv.controllerStatus) + api.GET("/info", srv.controllerInfo) + + api.POST("/update", srv.controllerUpdate) + api.POST("/reboot", srv.controllerReboot) + + // Error routes for testing + if mode.IsDev() { + router.GET("/error", srv.controllerError) + router.GET("/panic", srv.controllerPanic) + + api.GET("/error", srv.controllerError) + api.GET("/panic", srv.controllerPanic) + } ui.Register(router) @@ -66,40 +103,34 @@ func (srv *SEBRAUCServer) Run() error { return router.Run(srv.address) } -// @Description Start the update process -// @Route /update +// swagger:operation POST /update startUpdate // -// _Param {name} {in} {goType} {required} {description} -// @Param updateFile form file true "Rauc firmware image file (*.raucb)" +// Start the update process // -// _Success {status} {jsonType} {goType} {description} -// @Success 200 object statusMessage -// @Failure 500 object statusMessage "Server error" +// --- +// consumes: +// - multipart/form-data +// produces: [application/json] +// parameters: +// - name: updateFile +// in: formData +// description: RAUC firmware image file (*.raucb) +// required: true +// type: file +// responses: +// 200: +// description: Ok +// schema: +// $ref: "#/definitions/StatusMessage" +// 409: +// description: already running +// schema: +// $ref: "#/definitions/Error" +// 500: +// description: Server Error +// schema: +// $ref: "#/definitions/Error" func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) { - // swagger:operation POST /update startUpdate - // - // Start the update process - // - // --- - // consumes: - // - multipart/form-data - // produces: [application/json] - // parameters: - // - name: updateFile - // in: formData - // description: Rauc firmware image file (*.raucb) - // required: true - // type: file - // responses: - // 200: - // description: Ok - // schema: - // $ref: "#/definitions/StatusMessage" - // 500: - // description: Server Error - // schema: - // $ref: "#/definitions/StatusMessage" - file, err := c.FormFile("updateFile") if err != nil { c.Error(err) @@ -120,100 +151,93 @@ func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) { return } - err = srv.raucUpdater.RunRauc(updateFile) + err = srv.udater.RunRauc(updateFile) if err == nil { writeStatus(c, true, "Update started") } else if errors.Is(err, util.ErrAlreadyRunning) { - c.AbortWithError(409, errors.New("already running")) + writeStatus(c, false, "Updater already running") } else { c.Error(err) return } } +// swagger:operation GET /status getStatus +// +// Get the current status of the RAUC updater +// +// --- +// produces: [application/json] +// responses: +// 200: +// description: Ok +// schema: +// $ref: "#/definitions/RaucStatus" +// 500: +// description: Server Error +// schema: +// $ref: "#/definitions/Error" func (srv *SEBRAUCServer) controllerStatus(c *gin.Context) { - // swagger:operation GET /status getStatus - // - // Get the current status of the RAUC updater - // - // --- - // produces: [application/json] - // responses: - // 200: - // description: Ok - // schema: - // $ref: "#/definitions/RaucStatus" - // 500: - // description: Server Error - // schema: - // $ref: "#/definitions/StatusMessage" - - c.JSON(200, srv.raucUpdater.GetStatus()) + c.JSON(http.StatusOK, srv.udater.GetStatus()) } +// swagger:operation GET /info getInfo +// +// Get the current system info +// +// --- +// produces: [application/json] +// responses: +// 200: +// description: Ok +// schema: +// $ref: "#/definitions/SystemInfo" +// 500: +// description: Server Error +// schema: +// $ref: "#/definitions/Error" func (srv *SEBRAUCServer) controllerInfo(c *gin.Context) { - // swagger:operation GET /info getInfo - // - // Get the current system info - // - // --- - // produces: [application/json] - // responses: - // 200: - // description: Ok - // schema: - // $ref: "#/definitions/SystemInfo" - // 500: - // description: Server Error - // schema: - // $ref: "#/definitions/StatusMessage" - info, err := sysinfo.GetSysinfo() if err != nil { c.Error(err) } else { - c.JSON(200, info) + c.JSON(http.StatusOK, info) } } +// swagger:operation GET /reboot startReboot +// +// Reboot the system +// +// --- +// produces: [application/json] +// responses: +// 200: +// description: Ok +// schema: +// $ref: "#/definitions/StatusMessage" +// 500: +// description: Server Error +// schema: +// $ref: "#/definitions/Error" func (srv *SEBRAUCServer) controllerReboot(c *gin.Context) { - // swagger:operation GET /reboot startReboot - // - // Reboot the system - // - // --- - // produces: [application/json] - // responses: - // 200: - // description: Ok - // schema: - // $ref: "#/definitions/StatusMessage" - // 500: - // description: Server Error - // schema: - // $ref: "#/definitions/StatusMessage" - go util.Reboot(5 * time.Second) writeStatus(c, true, "System is rebooting") } -func errorHandler(c *gin.Context, err error) error { - // API error handling - if strings.HasPrefix(c.FullPath(), "/api") { - writeStatus(c, false, err.Error()) - } - return err +// controllerError throws an error for testing +func (srv *SEBRAUCServer) controllerError(c *gin.Context) { + c.Error(util.HttpErrNew("error test", http.StatusBadRequest)) +} + +// controllerPanic panics for testing +func (srv *SEBRAUCServer) controllerPanic(c *gin.Context) { + panic(errors.New("panic message")) } func writeStatus(c *gin.Context, success bool, msg string) { - status := 200 - - if !success { - status = 500 - } - - c.JSON(status, model.StatusMessage{ + c.JSON(http.StatusOK, model.StatusMessage{ Success: success, Msg: msg, }) diff --git a/src/server/swagger/docs.go b/src/server/swagger/docs.go deleted file mode 100644 index 198db73..0000000 --- a/src/server/swagger/docs.go +++ /dev/null @@ -1,11 +0,0 @@ -// SEBRAUC -// -// REST API for the SEBRAUC firmware updater -// -// --- -// Schemes: http, https -// Version: 0.2.0 -// License: MIT -// -// swagger:meta -package swagger diff --git a/src/server/swagger/swagger.go b/src/server/swagger/swagger.go index 732f009..e48ca00 100644 --- a/src/server/swagger/swagger.go +++ b/src/server/swagger/swagger.go @@ -3,6 +3,7 @@ package swagger import ( _ "embed" + "code.thetadev.de/TSGRain/SEBRAUC/src/server/middleware" "github.com/gin-gonic/gin" ) @@ -13,7 +14,7 @@ var swaggerHtml []byte var swaggerYaml []byte func Register(r *gin.Engine) { - swg := r.Group("/api/swagger") + swg := r.Group("/api/swagger", middleware.Cache) swg.GET("/", func(c *gin.Context) { c.Data(200, "text/html", swaggerHtml) diff --git a/src/server/swagger/swagger.yaml b/src/server/swagger/swagger.yaml index 24989f5..3d85982 100644 --- a/src/server/swagger/swagger.yaml +++ b/src/server/swagger/swagger.yaml @@ -1,5 +1,28 @@ definitions: + Error: + description: The Error contains error relevant information. + properties: + error: + description: The general error message according to HTTP specification. + example: Unauthorized + type: string + msg: + description: Concrete error message. + example: already running + type: string + status_code: + description: The http error code. + example: 500 + format: int64 + type: integer + required: + - error + - status_code + - msg + title: Error model + type: object RaucStatus: + description: RaucStatus contains information about the current RAUC updater status. properties: installing: description: True if the installer is running @@ -29,6 +52,7 @@ definitions: - message - last_error - log + title: RaucStatus model type: object Rootfs: properties: @@ -62,6 +86,7 @@ definitions: - primary type: object StatusMessage: + description: StatusMessage contains the status of an operation. properties: msg: description: Status message text @@ -73,8 +98,10 @@ definitions: required: - success - msg + title: StatusMessage model type: object SystemInfo: + description: SystemInfo contains information about the running system. properties: hostname: description: Hostname of the system @@ -114,6 +141,7 @@ definitions: - rauc_compatible - rauc_variant - rauc_rootfs + title: SystemInfo model type: object info: description: REST API for the SEBRAUC firmware updater @@ -136,7 +164,7 @@ paths: "500": description: Server Error schema: - $ref: "#/definitions/StatusMessage" + $ref: "#/definitions/Error" /reboot: get: description: Reboot the system @@ -151,7 +179,7 @@ paths: "500": description: Server Error schema: - $ref: "#/definitions/StatusMessage" + $ref: "#/definitions/Error" /status: get: description: Get the current status of the RAUC updater @@ -166,7 +194,7 @@ paths: "500": description: Server Error schema: - $ref: "#/definitions/StatusMessage" + $ref: "#/definitions/Error" /update: post: consumes: @@ -174,7 +202,7 @@ paths: description: Start the update process operationId: startUpdate parameters: - - description: Rauc firmware image file (*.raucb) + - description: RAUC firmware image file (*.raucb) in: formData name: updateFile required: true @@ -186,10 +214,12 @@ paths: description: Ok schema: $ref: "#/definitions/StatusMessage" + "409": + description: already running "500": description: Server Error schema: - $ref: "#/definitions/StatusMessage" + $ref: "#/definitions/Error" schemes: - http - https diff --git a/src/util/commands.go b/src/util/commands.go index a157fa0..78fb45d 100644 --- a/src/util/commands.go +++ b/src/util/commands.go @@ -3,9 +3,11 @@ package util +import "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" + const ( RebootCmd = "shutdown -r 0" RaucCmd = "rauc" - TestMode = false + Mode = mode.Prod ) diff --git a/src/util/commands_mock.go b/src/util/commands_mock.go index d4d1392..b447055 100644 --- a/src/util/commands_mock.go +++ b/src/util/commands_mock.go @@ -3,9 +3,11 @@ package util +import "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" + const ( RebootCmd = "touch /tmp/sebrauc_reboot_test" RaucCmd = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock" - TestMode = true + Mode = mode.Dev ) diff --git a/src/util/errors.go b/src/util/errors.go index 20ee9e4..1d3fc3b 100644 --- a/src/util/errors.go +++ b/src/util/errors.go @@ -1,8 +1,12 @@ package util -import "errors" +import ( + "errors" + "net/http" +) var ( ErrAlreadyRunning = errors.New("rauc already running") ErrFileDoesNotExist = errors.New("file does not exist") + ErrPageNotFound = HttpErrNew("page not found", http.StatusNotFound) ) diff --git a/src/util/http_error.go b/src/util/http_error.go new file mode 100644 index 0000000..c5d7cf8 --- /dev/null +++ b/src/util/http_error.go @@ -0,0 +1,39 @@ +package util + +import "errors" + +type HttpError interface { + error + StatusCode() int +} + +type httpErr struct { + err error + statusCode int +} + +func HttpErrWrap(e error, statusCode int) HttpError { + return &httpErr{ + err: e, + statusCode: statusCode, + } +} + +func HttpErrNew(msg string, statusCode int) HttpError { + return HttpErrWrap(errors.New(msg), statusCode) +} + +func (e *httpErr) Error() string { + if e.err == nil { + return "" + } + return e.err.Error() +} + +func (e *httpErr) Unwrap() error { + return e.err +} + +func (e *httpErr) StatusCode() int { + return e.statusCode +} diff --git a/src/util/types.go b/src/util/types.go new file mode 100644 index 0000000..afd106b --- /dev/null +++ b/src/util/types.go @@ -0,0 +1,5 @@ +package util + +type Broadcaster interface { + Broadcast(msg []byte) +} diff --git a/ui/src/sebrauc-client/api.ts b/ui/src/sebrauc-client/api.ts index 015ad91..c98ab0a 100644 --- a/ui/src/sebrauc-client/api.ts +++ b/ui/src/sebrauc-client/api.ts @@ -38,7 +38,32 @@ import { } from "./base" /** - * + * The Error contains error relevant information. + * @export + * @interface ModelError + */ +export interface ModelError { + /** + * The general error message according to HTTP specification. + * @type {string} + * @memberof ModelError + */ + error: string + /** + * Concrete error message. + * @type {string} + * @memberof ModelError + */ + msg: string + /** + * The http error code. + * @type {number} + * @memberof ModelError + */ + status_code: number +} +/** + * RaucStatus contains information about the current RAUC updater status. * @export * @interface RaucStatus */ @@ -118,7 +143,7 @@ export interface Rootfs { type: string } /** - * + * StatusMessage contains the status of an operation. * @export * @interface StatusMessage */ @@ -137,7 +162,7 @@ export interface StatusMessage { success: boolean } /** - * + * SystemInfo contains information about the running system. * @export * @interface SystemInfo */ @@ -290,7 +315,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati }, /** * Start the update process - * @param {any} updateFile Rauc firmware image file (*.raucb) + * @param {any} updateFile RAUC firmware image file (*.raucb) * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -404,7 +429,7 @@ export const DefaultApiFp = function (configuration?: Configuration) { }, /** * Start the update process - * @param {any} updateFile Rauc firmware image file (*.raucb) + * @param {any} updateFile RAUC firmware image file (*.raucb) * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -471,7 +496,7 @@ export const DefaultApiFactory = function ( }, /** * Start the update process - * @param {any} updateFile Rauc firmware image file (*.raucb) + * @param {any} updateFile RAUC firmware image file (*.raucb) * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -528,7 +553,7 @@ export class DefaultApi extends BaseAPI { /** * Start the update process - * @param {any} updateFile Rauc firmware image file (*.raucb) + * @param {any} updateFile RAUC firmware image file (*.raucb) * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof DefaultApi diff --git a/ui/ui.go b/ui/ui.go index a10fda6..66329e8 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -7,6 +7,7 @@ import ( "io/fs" "net/http" + "code.thetadev.de/TSGRain/SEBRAUC/src/server/middleware" "code.thetadev.de/TSGRain/SEBRAUC/src/util" "code.thetadev.de/TSGRain/ginzip" "github.com/gin-gonic/gin" @@ -36,7 +37,7 @@ func distFS() fs.FS { func Register(r *gin.Engine) { indexHandler := getIndexHandler() - ui := r.Group("/", ginzip.New(ginzip.DefaultOptions())) + ui := r.Group("/", ginzip.New(ginzip.DefaultOptions()), middleware.Cache) ui.GET("/", indexHandler) ui.GET("/index.html", indexHandler) From 4cc757d55064fdfb8535146499a09218246a8228 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 19 Dec 2021 22:29:12 +0100 Subject: [PATCH 14/27] add tests for rauc, ui and middleware --- .air.toml | 4 +- .golangci.yaml | 1 + go.mod | 17 ++- go.sum | 53 +++++--- src/fixtures/rauc_mock/main.go | 36 +++-- src/main.go | 3 +- src/model/rauc_status.go | 1 + src/rauc/rauc.go | 15 ++- src/rauc/rauc_test.go | 116 ++++++++++++++++ src/server/middleware/cache_test.go | 25 ++++ src/server/middleware/error_handler.go | 2 +- src/server/middleware/error_handler_test.go | 140 ++++++++++++++++++++ src/server/server.go | 2 +- src/server/stream/once_test.go | 2 +- src/server/stream/stream.go | 5 +- src/server/stream/stream_test.go | 2 +- src/server/swagger/swagger_test.go | 44 ++++++ src/util/commands.go | 4 +- src/util/commands_mock.go | 4 +- src/{server => util}/mode/mode.go | 7 +- src/{server => util}/mode/mode_test.go | 0 src/util/version.go | 6 + ui/ui_test.go | 23 ++++ 23 files changed, 463 insertions(+), 49 deletions(-) create mode 100644 src/rauc/rauc_test.go create mode 100644 src/server/middleware/cache_test.go create mode 100644 src/server/middleware/error_handler_test.go create mode 100644 src/server/swagger/swagger_test.go rename src/{server => util}/mode/mode.go (89%) rename src/{server => util}/mode/mode_test.go (100%) create mode 100644 ui/ui_test.go diff --git a/.air.toml b/.air.toml index 9520ff3..91be4cb 100644 --- a/.air.toml +++ b/.air.toml @@ -1,11 +1,11 @@ -root = "./src" +root = "." tmp_dir = "tmp" [build] bin = "./tmp/main" cmd = "go build -o ./tmp/main ./src/." delay = 1000 - exclude_dir = ["assets", "tmp", "vendor"] + exclude_dir = ["tmp", "vendor", "ui/dist", "ui/node_modules", "ui/src"] exclude_file = [] exclude_regex = [] exclude_unchanged = false diff --git a/.golangci.yaml b/.golangci.yaml index 31a4beb..2e06378 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -18,6 +18,7 @@ linters: linters-settings: lll: line-length: 88 + tab-width: 4 gocognit: min-complexity: 10 nestif: diff --git a/go.mod b/go.mod index 2090ee2..8453017 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,21 @@ require ( github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.7.7 github.com/go-errors/errors v1.4.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.6 // indirect github.com/google/uuid v1.3.0 - github.com/gorilla/websocket v1.4.2 + github.com/gorilla/websocket v1.4.1 + github.com/json-iterator/go v1.1.12 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/stretchr/testify v1.7.0 - golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect - golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 7011440..8cd3adf 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ code.thetadev.de/TSGRain/ginzip v0.1.1 h1:+X0L6qumEZiKYSLmM+Q0LqKVHsKvdcg4CVzsEp code.thetadev.de/TSGRain/ginzip v0.1.1/go.mod h1:BH7VkvpP83vPRyMQ8rLIjKycQwGzF+/mFV0BKzg+BuA= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -29,31 +30,42 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+ github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -67,8 +79,8 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -76,21 +88,32 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/fixtures/rauc_mock/main.go b/src/fixtures/rauc_mock/main.go index 58986ff..228aeda 100644 --- a/src/fixtures/rauc_mock/main.go +++ b/src/fixtures/rauc_mock/main.go @@ -48,27 +48,47 @@ const statusJson = `{"compatible":"TSGRain","variant":"dev","booted":"A",` + `{"class":"rootfs","device":"/dev/mmcblk0p2","type":"ext4","bootname":"A",` + `"state":"booted","parent":null,"mountpoint":"/","boot_status":"good"}}]}` -func printLinesWithDelay(lines string) { +func printLinesWithDelay(lines string, delay time.Duration) { for _, line := range strings.Split(lines, "\n") { fmt.Println(line) - time.Sleep(500 * time.Millisecond) + time.Sleep(delay) } } +func getBoolEnvvar(name string) bool { + val := strings.ToLower(os.Getenv(name)) + return val != "" && val != "false" && val != "0" +} + func main() { - arg := "" + method := "" if len(os.Args) > 1 { - arg = os.Args[1] + method = os.Args[1] } - switch arg { - case "fail": - printLinesWithDelay(outputFailure) + test := getBoolEnvvar("RAUC_MOCK_TEST") + failure := getBoolEnvvar("RAUC_MOCK_FAIL") + + delay := 500 * time.Millisecond + if test { + delay = 10 * time.Millisecond + } + + switch method { case "install": - printLinesWithDelay(outputSuccess) + if failure { + printLinesWithDelay(outputFailure, delay) + } else { + printLinesWithDelay(outputSuccess, delay) + } case "status": + if os.Args[2] != "--output-format=json" { + fmt.Println("output format must be json") + os.Exit(1) + } fmt.Println(statusJson) default: + fmt.Println("invalid method") os.Exit(1) } } diff --git a/src/main.go b/src/main.go index 0bbf0f0..8cbc312 100644 --- a/src/main.go +++ b/src/main.go @@ -7,14 +7,13 @@ import ( "code.thetadev.de/TSGRain/SEBRAUC/src/rauc" "code.thetadev.de/TSGRain/SEBRAUC/src/server" - "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" "code.thetadev.de/TSGRain/SEBRAUC/src/server/stream" "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" ) func main() { fmt.Println("SEBRAUC " + util.Version()) - mode.Set(util.Mode) if mode.IsDev() { fmt.Println("Test mode active - no update operations are executed.") diff --git a/src/model/rauc_status.go b/src/model/rauc_status.go index 3a48b53..eb0d92d 100644 --- a/src/model/rauc_status.go +++ b/src/model/rauc_status.go @@ -5,6 +5,7 @@ package model // RaucStatus contains information about the current RAUC updater status. // //swagger:model RaucStatus +//nolint:lll type RaucStatus struct { // True if the installer is running // required: true diff --git a/src/rauc/rauc.go b/src/rauc/rauc.go index e84b142..691225c 100644 --- a/src/rauc/rauc.go +++ b/src/rauc/rauc.go @@ -26,12 +26,12 @@ type Rauc struct { func (r *Rauc) SetBroadcaster(bc util.Broadcaster) { r.bc = bc - r.bc.Broadcast(r.GetStatusJson()) + r.bcStatus() } func (r *Rauc) completed(updateFile string) { r.status.Installing = false - r.bc.Broadcast(r.GetStatusJson()) + r.bcStatus() _ = os.Remove(updateFile) } @@ -56,9 +56,10 @@ func (r *Rauc) RunRauc(updateFile string) error { r.status = model.RaucStatus{ Installing: true, } - r.bc.Broadcast(r.GetStatusJson()) + r.bcStatus() - cmd := util.CommandFromString(fmt.Sprintf("%s install %s", util.RaucCmd, updateFile)) + cmd := util.CommandFromString( + fmt.Sprintf("%s install %s", util.RaucCmd, updateFile)) readPipe, _ := cmd.StdoutPipe() cmd.Stderr = cmd.Stdout @@ -88,7 +89,7 @@ func (r *Rauc) RunRauc(updateFile string) error { } if hasUpdate { - r.bc.Broadcast(r.GetStatusJson()) + r.bcStatus() } } }() @@ -118,3 +119,7 @@ func (r *Rauc) GetStatusJson() []byte { statusJson, _ := json.Marshal(r.status) return statusJson } + +func (r *Rauc) bcStatus() { + r.bc.Broadcast(r.GetStatusJson()) +} diff --git a/src/rauc/rauc_test.go b/src/rauc/rauc_test.go new file mode 100644 index 0000000..df11109 --- /dev/null +++ b/src/rauc/rauc_test.go @@ -0,0 +1,116 @@ +package rauc + +import ( + "os" + "path/filepath" + "testing" + "time" + + "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "github.com/stretchr/testify/assert" +) + +type broadcasterMock struct { + messages []string +} + +func (b *broadcasterMock) Broadcast(msg []byte) { + b.messages = append(b.messages, string(msg)) +} + +func TestRauc(t *testing.T) { + //nolint:lll + tests := []struct { + name string + fail string + messages []string + }{ + { + name: "ok", + fail: "", + messages: []string{ + "{\"installing\":false,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"Installing\",\"last_error\":\"\",\"log\":\"0% Installing\\n\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"Determining slot states\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n\"}", + "{\"installing\":true,\"percent\":20,\"message\":\"Determining slot states done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n\"}", + "{\"installing\":true,\"percent\":20,\"message\":\"Checking bundle\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n\"}", + "{\"installing\":true,\"percent\":20,\"message\":\"Verifying signature\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n\"}", + "{\"installing\":true,\"percent\":40,\"message\":\"Verifying signature done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n\"}", + "{\"installing\":true,\"percent\":40,\"message\":\"Checking bundle done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n\"}", + "{\"installing\":true,\"percent\":40,\"message\":\"Checking manifest contents\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n\"}", + "{\"installing\":true,\"percent\":60,\"message\":\"Checking manifest contents done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n\"}", + "{\"installing\":true,\"percent\":60,\"message\":\"Determining target install group\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n\"}", + "{\"installing\":true,\"percent\":80,\"message\":\"Determining target install group done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n\"}", + "{\"installing\":true,\"percent\":80,\"message\":\"Updating slots\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n\"}", + "{\"installing\":true,\"percent\":80,\"message\":\"Checking slot rootfs.0\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n\"}", + "{\"installing\":true,\"percent\":90,\"message\":\"Checking slot rootfs.0 done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n\"}", + "{\"installing\":true,\"percent\":90,\"message\":\"Copying image to rootfs.0\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n\"}", + "{\"installing\":true,\"percent\":100,\"message\":\"Copying image to rootfs.0 done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n\"}", + "{\"installing\":true,\"percent\":100,\"message\":\"Updating slots done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n100% Updating slots done.\\n\"}", + "{\"installing\":true,\"percent\":100,\"message\":\"Installing done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n100% Updating slots done.\\n100% Installing done.\\n\"}", + "{\"installing\":false,\"percent\":100,\"message\":\"Installing done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n100% Updating slots done.\\n100% Installing done.\\nInstalling `/app/tsgrain-update-raspberrypi3.raucb` succeeded\\n\"}", + }, + }, + { + name: "fail", + fail: "1", + messages: []string{ + "{\"installing\":false,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"Installing\",\"last_error\":\"\",\"log\":\"0% Installing\\n\"}", + "{\"installing\":true,\"percent\":0,\"message\":\"Determining slot states\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n\"}", + "{\"installing\":true,\"percent\":20,\"message\":\"Determining slot states done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n\"}", + "{\"installing\":true,\"percent\":20,\"message\":\"Checking bundle\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n\"}", + "{\"installing\":true,\"percent\":40,\"message\":\"Checking bundle failed.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n\"}", + "{\"installing\":true,\"percent\":100,\"message\":\"Installing failed.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n100% Installing failed.\\n\"}", + "{\"installing\":true,\"percent\":100,\"message\":\"Installing failed.\",\"last_error\":\"Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n100% Installing failed.\\nLastError: Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\\n\"}", + "{\"installing\":false,\"percent\":100,\"message\":\"Installing failed.\",\"last_error\":\"Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n100% Installing failed.\\nLastError: Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\\nInstalling /app/demo` failed\\n\"}", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + os.Setenv("RAUC_MOCK_TEST", "1") + os.Setenv("RAUC_MOCK_FAIL", tt.fail) + + updater := &Rauc{} + bc := &broadcasterMock{} + updater.SetBroadcaster(bc) + + testfile := createTmpfile() + + err := updater.RunRauc(testfile) + assert.NoError(t, err) + + // Dont run multiple updates concurrently + err = updater.RunRauc(testfile) + assert.ErrorIs(t, err, util.ErrAlreadyRunning) + + // Wait for updater to finish + for updater.GetStatus().Installing { + time.Sleep(50 * time.Millisecond) + } + + assert.False(t, util.DoesFileExist(testfile), "update file was not deleted") + + assert.Equal(t, tt.messages, bc.messages) + }) + } +} + +func createTmpfile() string { + tmpdir, err := util.GetTmpdir() + if err != nil { + panic(err) + } + + tmpfile := filepath.Join(tmpdir, "test.raucb") + _, err = os.Create(tmpfile) + if err != nil { + panic(err) + } + + return tmpfile +} diff --git a/src/server/middleware/cache_test.go b/src/server/middleware/cache_test.go new file mode 100644 index 0000000..eaf7f84 --- /dev/null +++ b/src/server/middleware/cache_test.go @@ -0,0 +1,25 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestCache(t *testing.T) { + router := gin.New() + router.Use(Cache) + router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "HelloWorld") }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "HelloWorld", w.Body.String()) + assert.Equal(t, "public, max-age=604800, immutable", + w.Header().Get("Cache-Control")) +} diff --git a/src/server/middleware/error_handler.go b/src/server/middleware/error_handler.go index e774c66..94aac0f 100644 --- a/src/server/middleware/error_handler.go +++ b/src/server/middleware/error_handler.go @@ -26,7 +26,7 @@ func ErrorHandler(isApi bool) gin.HandlerFunc { func PanicHandler(isApi bool) gin.HandlerFunc { return nice.Recovery(func(c *gin.Context, err interface{}) { - writeError(c, fmt.Errorf("panic: %s", err), isApi) + writeError(c, fmt.Errorf("[PANIC] %s", err), isApi) }) } diff --git a/src/server/middleware/error_handler_test.go b/src/server/middleware/error_handler_test.go new file mode 100644 index 0000000..4efedb1 --- /dev/null +++ b/src/server/middleware/error_handler_test.go @@ -0,0 +1,140 @@ +package middleware + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestErrorHandler(t *testing.T) { + tests := []struct { + name string + controller gin.HandlerFunc + isApi bool + expectResponse string + expectStatus int + }{ + { + name: "error", + controller: controllerError, + isApi: false, + expectResponse: "400 Bad Request: error test", + expectStatus: http.StatusBadRequest, + }, + { + name: "error_api", + controller: controllerError, + isApi: true, + //nolint:lll + expectResponse: `{"error":"Bad Request","status_code":400,"msg":"error test"}`, + expectStatus: http.StatusBadRequest, + }, + { + name: "generic_error", + controller: controllerErrorGeneric, + isApi: false, + expectResponse: "500 Internal Server Error: generic error", + expectStatus: http.StatusInternalServerError, + }, + { + name: "generic_error_api", + controller: controllerErrorGeneric, + isApi: true, + //nolint:lll + expectResponse: `{"error":"Internal Server Error","status_code":500,"msg":"generic error"}`, + expectStatus: http.StatusInternalServerError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router := gin.New() + router.Use(ErrorHandler(tt.isApi)) + router.GET("/", tt.controller) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, tt.expectStatus, w.Code) + assert.Equal(t, tt.expectResponse, w.Body.String()) + }) + } +} + +func TestPanicHandler(t *testing.T) { + tests := []struct { + name string + controller gin.HandlerFunc + isApi bool + expectResponse string + expectStatus int + }{ + { + name: "panic", + controller: controllerPanic, + isApi: false, + expectResponse: "500 Internal Server Error: [PANIC] panic message", + expectStatus: http.StatusInternalServerError, + }, + { + name: "panic_api", + controller: controllerPanic, + isApi: true, + //nolint:lll + expectResponse: `{"error":"Internal Server Error","status_code":500,"msg":"[PANIC] panic message"}`, + expectStatus: http.StatusInternalServerError, + }, + { + name: "panic_w_error", + controller: controllerPanicErr, + isApi: false, + expectResponse: "500 Internal Server Error: [PANIC] panic message in error", + expectStatus: http.StatusInternalServerError, + }, + { + name: "panic_w_error_api", + controller: controllerPanicErr, + isApi: true, + //nolint:lll + expectResponse: `{"error":"Internal Server Error","status_code":500,"msg":"[PANIC] panic message in error"}`, + expectStatus: http.StatusInternalServerError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router := gin.New() + router.Use(PanicHandler(tt.isApi)) + router.GET("/", tt.controller) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, tt.expectStatus, w.Code) + assert.Equal(t, tt.expectResponse, w.Body.String()) + }) + } +} + +func controllerError(c *gin.Context) { + c.Error(util.HttpErrNew("error test", http.StatusBadRequest)) +} + +func controllerErrorGeneric(c *gin.Context) { + c.Error(errors.New("generic error")) +} + +func controllerPanic(c *gin.Context) { + panic("panic message") +} + +func controllerPanicErr(c *gin.Context) { + panic(errors.New("panic message in error")) +} diff --git a/src/server/server.go b/src/server/server.go index 3f1eafb..1244697 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -18,10 +18,10 @@ import ( "code.thetadev.de/TSGRain/SEBRAUC/src/model" "code.thetadev.de/TSGRain/SEBRAUC/src/server/middleware" - "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" "code.thetadev.de/TSGRain/SEBRAUC/src/server/swagger" "code.thetadev.de/TSGRain/SEBRAUC/src/sysinfo" "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" "code.thetadev.de/TSGRain/SEBRAUC/ui" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" diff --git a/src/server/stream/once_test.go b/src/server/stream/once_test.go index 53ec08d..720f65b 100644 --- a/src/server/stream/once_test.go +++ b/src/server/stream/once_test.go @@ -20,7 +20,7 @@ func Test_Execute(t *testing.T) { case <-execution: // expected case <-time.After(100 * time.Millisecond): - t.Fatal("fExecute should be executed once") + t.Fatal("Execute should be executed once") } select { diff --git a/src/server/stream/stream.go b/src/server/stream/stream.go index a60c624..1e58ed6 100644 --- a/src/server/stream/stream.go +++ b/src/server/stream/stream.go @@ -8,8 +8,8 @@ import ( "sync" "time" - "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) @@ -146,7 +146,8 @@ func newUpgrader(allowedWebSocketOrigins []string) *websocket.Upgrader { func compileAllowedWebSocketOrigins(allowedOrigins []string) []*regexp.Regexp { var compiledAllowedOrigins []*regexp.Regexp for _, origin := range allowedOrigins { - compiledAllowedOrigins = append(compiledAllowedOrigins, regexp.MustCompile(origin)) + compiledAllowedOrigins = append(compiledAllowedOrigins, + regexp.MustCompile(origin)) } return compiledAllowedOrigins diff --git a/src/server/stream/stream_test.go b/src/server/stream/stream_test.go index 69f6dc2..ca24cca 100644 --- a/src/server/stream/stream_test.go +++ b/src/server/stream/stream_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" + "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" "github.com/fortytw2/leaktest" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" diff --git a/src/server/swagger/swagger_test.go b/src/server/swagger/swagger_test.go new file mode 100644 index 0000000..9fd3c03 --- /dev/null +++ b/src/server/swagger/swagger_test.go @@ -0,0 +1,44 @@ +package swagger + +import ( + "bytes" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestSwagger(t *testing.T) { + router := gin.New() + Register(router) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/api/swagger/", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, swaggerHtml, w.Body.Bytes()) + assert.NotEmpty(t, w.Header().Get("Cache-Control")) + + w = httptest.NewRecorder() + req, _ = http.NewRequest("GET", "/api/swagger/swagger.yaml", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, swaggerYaml, w.Body.Bytes()) + assert.NotEmpty(t, w.Header().Get("Cache-Control")) +} + +func TestSwaggerData(t *testing.T) { + assert.True(t, bytes.Contains(swaggerHtml, + []byte("https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js")), + "HTML data missing", + ) + + assert.True(t, bytes.Contains(swaggerYaml, + []byte("REST API for the SEBRAUC firmware updater")), + "YAML data missing", + ) +} diff --git a/src/util/commands.go b/src/util/commands.go index 78fb45d..3341b90 100644 --- a/src/util/commands.go +++ b/src/util/commands.go @@ -3,11 +3,11 @@ package util -import "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" +import "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" const ( RebootCmd = "shutdown -r 0" RaucCmd = "rauc" - Mode = mode.Prod + appmode = mode.Prod ) diff --git a/src/util/commands_mock.go b/src/util/commands_mock.go index b447055..f52d17d 100644 --- a/src/util/commands_mock.go +++ b/src/util/commands_mock.go @@ -3,11 +3,11 @@ package util -import "code.thetadev.de/TSGRain/SEBRAUC/src/server/mode" +import "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" const ( RebootCmd = "touch /tmp/sebrauc_reboot_test" RaucCmd = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock" - Mode = mode.Dev + appmode = mode.Dev ) diff --git a/src/server/mode/mode.go b/src/util/mode/mode.go similarity index 89% rename from src/server/mode/mode.go rename to src/util/mode/mode.go index 59e297f..c8625f6 100644 --- a/src/server/mode/mode.go +++ b/src/util/mode/mode.go @@ -11,17 +11,16 @@ const ( TestDev = "testdev" ) -var mode = Dev +var currentMode = Dev -// Set sets the new mode. func Set(newMode string) { - mode = newMode + currentMode = newMode updateGinMode() } // Get returns the current mode. func Get() string { - return mode + return currentMode } // IsDev returns true if the current mode is dev mode. diff --git a/src/server/mode/mode_test.go b/src/util/mode/mode_test.go similarity index 100% rename from src/server/mode/mode_test.go rename to src/util/mode/mode_test.go diff --git a/src/util/version.go b/src/util/version.go index 7e8dba4..e9a1869 100644 --- a/src/util/version.go +++ b/src/util/version.go @@ -1,7 +1,13 @@ package util +import "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" + var version = "dev" +func init() { + mode.Set(appmode) +} + func Version() string { return version } diff --git a/ui/ui_test.go b/ui/ui_test.go new file mode 100644 index 0000000..acb0d46 --- /dev/null +++ b/ui/ui_test.go @@ -0,0 +1,23 @@ +package ui + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestUI(t *testing.T) { + router := gin.New() + Register(router) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "SEBRAUC") + assert.NotEmpty(t, w.Header().Get("Cache-Control")) +} From e3cb739db58f9d88da3c3f11deee70cf773dc84a Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 19 Dec 2021 22:37:43 +0100 Subject: [PATCH 15/27] fix SysinfoCard: reloader not cleared --- ui/src/components/Updater/SysinfoCard.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ui/src/components/Updater/SysinfoCard.tsx b/ui/src/components/Updater/SysinfoCard.tsx index 04bd51c..625ed7c 100644 --- a/ui/src/components/Updater/SysinfoCard.tsx +++ b/ui/src/components/Updater/SysinfoCard.tsx @@ -23,6 +23,8 @@ type State = { } export default class SysinfoCard extends Component { + private fetchTimeout: number | undefined + constructor(props?: Props | undefined, context?: any) { super(props, context) this.fetchInfo() @@ -36,13 +38,12 @@ export default class SysinfoCard extends Component { this.setState({sysinfo: response.data}) } else { console.log("error fetching info", response.data) - console.log("error fetching info", response.data) - window.setTimeout(this.fetchInfo, 3000) + this.fetchTimeout = window.setTimeout(this.fetchInfo, 3000) } }) .catch((reason) => { console.log("error fetching info", reason) - window.setTimeout(this.fetchInfo, 3000) + this.fetchTimeout = window.setTimeout(this.fetchInfo, 3000) }) } @@ -150,6 +151,12 @@ export default class SysinfoCard extends Component { ) } + componentWillUnmount() { + if (this.fetchTimeout !== undefined) { + window.clearTimeout(this.fetchTimeout) + } + } + render() { if (this.state.sysinfo) { return this.renderSysinfo() From 3c9867b75b7a6497dee38e2b8970bf6166858116 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 19 Dec 2021 22:44:30 +0100 Subject: [PATCH 16/27] fix apidoc --- src/server/server.go | 4 +--- src/server/swagger/swagger.yaml | 2 ++ src/util/errors.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server/server.go b/src/server/server.go index 1244697..74a2815 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -124,7 +124,7 @@ func (srv *SEBRAUCServer) Run() error { // $ref: "#/definitions/StatusMessage" // 409: // description: already running -// schema: +// schema: // $ref: "#/definitions/Error" // 500: // description: Server Error @@ -154,8 +154,6 @@ func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) { err = srv.udater.RunRauc(updateFile) if err == nil { writeStatus(c, true, "Update started") - } else if errors.Is(err, util.ErrAlreadyRunning) { - writeStatus(c, false, "Updater already running") } else { c.Error(err) return diff --git a/src/server/swagger/swagger.yaml b/src/server/swagger/swagger.yaml index 3d85982..fb137ff 100644 --- a/src/server/swagger/swagger.yaml +++ b/src/server/swagger/swagger.yaml @@ -216,6 +216,8 @@ paths: $ref: "#/definitions/StatusMessage" "409": description: already running + schema: + $ref: "#/definitions/Error" "500": description: Server Error schema: diff --git a/src/util/errors.go b/src/util/errors.go index 1d3fc3b..62427f8 100644 --- a/src/util/errors.go +++ b/src/util/errors.go @@ -6,7 +6,7 @@ import ( ) var ( - ErrAlreadyRunning = errors.New("rauc already running") + ErrAlreadyRunning = HttpErrNew("rauc already running", http.StatusConflict) ErrFileDoesNotExist = errors.New("file does not exist") ErrPageNotFound = HttpErrNew("page not found", http.StatusNotFound) ) From 312de77236df531b2eb7796158db66e25e0ad72e Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Thu, 23 Dec 2021 23:45:59 +0100 Subject: [PATCH 17/27] add configfile --- go.mod | 1 + go.sum | 4 + sebrauc.example.toml | 40 ++++++++ src/config/config.go | 120 ++++++++++++++++++++++ src/config/config_test.go | 150 ++++++++++++++++++++++++++++ src/fixtures/testfiles/sebrauc.toml | 28 ++++++ src/server/stream/stream_test.go | 25 +++++ 7 files changed, 368 insertions(+) create mode 100644 sebrauc.example.toml create mode 100644 src/config/config.go create mode 100644 src/config/config_test.go create mode 100644 src/fixtures/testfiles/sebrauc.toml diff --git a/go.mod b/go.mod index 8453017..6e59bf6 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/google/go-cmp v0.5.6 // indirect github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.1 + github.com/jinzhu/configor v1.2.1 github.com/json-iterator/go v1.1.12 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-isatty v0.0.14 // indirect diff --git a/go.sum b/go.sum index 8cd3adf..376c179 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ code.thetadev.de/TSGRain/ginzip v0.1.1 h1:+X0L6qumEZiKYSLmM+Q0LqKVHsKvdcg4CVzsEpvM7fk= code.thetadev.de/TSGRain/ginzip v0.1.1/go.mod h1:BH7VkvpP83vPRyMQ8rLIjKycQwGzF+/mFV0BKzg+BuA= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -42,6 +44,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko= +github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/sebrauc.example.toml b/sebrauc.example.toml new file mode 100644 index 0000000..6ae00e1 --- /dev/null +++ b/sebrauc.example.toml @@ -0,0 +1,40 @@ +# SEBRAUC config file example + +# Root temporary directory +# Update packages are stored temporarily under Tmpdir/sebrauc/update-.raucb +Tmpdir = "/tmp" + +# Webserver options +[Server] +# IP address to accept connections from +Address = "" +# Port to listen at +Port = 8001 + +# Websocket connection settings: Ping interval/Timeout in seconds +[Server.Websocket] +Ping = 45 +Timeout = 15 + +# Compression settings. Refer to code.thetadev.de/TSGRain/ginzip for details. +[Server.Compression] +Gzip = "default" +Brotli = "default" + +# Where to obtain system info from +[Sysinfo] +ReleaseFile = "/etc/os-release" +# Keys to look for in the OS release file +NameKey = "NAME" +VersionKey = "VERSION" +HostnameFile = "/etc/hostname" +UptimeFile = "/proc/uptime" + +# Commands to be run by SEBRAUC +[Commands] +# RAUC status command (outputs updater status in json format) +RaucStatus = "rauc status --output-format=json" +# RAUC install command (installs FW image passed as argument) +RaucInstall = "rauc install" +# System reboot command +Reboot = "shutdown -r 0" diff --git a/src/config/config.go b/src/config/config.go new file mode 100644 index 0000000..5a7d0a9 --- /dev/null +++ b/src/config/config.go @@ -0,0 +1,120 @@ +package config + +import ( + "strings" + + "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" + "github.com/jinzhu/configor" +) + +// SEBRAUC config object +type Config struct { + // Root temporary directory + // Update packages are stored temporarily under Tmpdir/sebrauc/update-.raucb + Tmpdir string `default:"/tmp"` + + // Webserver options + Server struct { + // IP address to accept connections from + Address string + // Port to listen at + Port int `default:"80"` + + // Websocket connection settings: Ping interval/Timeout in seconds + Websocket struct { + Ping int `default:"45"` + Timeout int `default:"15"` + } + + // Compression settings. Refer to code.thetadev.de/TSGRain/ginzip for details. + Compression struct { + Gzip string + Brotli string + } + } + + // Where to obtain system info from + Sysinfo struct { + ReleaseFile string `default:"/etc/os-release"` + // Keys to look for in the OS release file + NameKey string `default:"NAME"` + VersionKey string `default:"VERSION"` + HostnameFile string `default:"/etc/hostname"` + UptimeFile string `default:"/proc/uptime"` + } + + // Commands to be run by SEBRAUC + // Note that these are overriden when running in development mode + Commands struct { + // RAUC status command (outputs updater status in json format) + RaucStatus string `default:"rauc status --output-format=json"` + // RAUC install command (installs FW image passed as argument) + RaucInstall string `default:"rauc install"` + // System reboot command + Reboot string `default:"shutdown -r 0"` + } +} + +func findConfigFile(pathIn string) string { + if pathIn != "" { + if !util.DoesFileExist(pathIn) { + panic("specified cfg file does not exist") + } + return pathIn + } + + filePaths := []string{"sebrauc", "/etc/sebrauc"} + fileTypes := []string{"toml", "yaml", "yml", "json"} + + for _, f := range filePaths { + for _, t := range fileTypes { + fpath := f + "." + t + if util.DoesFileExist(fpath) { + return fpath + } + } + } + return "" +} + +func stripTrailingSlashes(pathIn string) string { + return strings.TrimRight(pathIn, "/") +} + +// GetWithFlags returns the configuration extracted from cmdline args, +// env variables or config file. +func GetWithFlags(pathIn string, portIn int) *Config { + cfg := new(Config) + err := configor.New(&configor.Config{ + // Debug: true, + // Verbose: true, + ENVPrefix: "SEBRAUC", + }).Load(cfg, findConfigFile(pathIn)) + if err != nil { + panic(err) + } + + cfg.Tmpdir = stripTrailingSlashes(cfg.Tmpdir) + + // Override port with cmdline flag if set + if portIn > 0 { + cfg.Server.Port = portIn + } + + // Override commands with testing options + if mode.IsDev() { + //nolint:lll + cfg.Commands.RaucStatus = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock status --output-format=json" + //nolint:lll + cfg.Commands.RaucInstall = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock install" + cfg.Commands.Reboot = "touch /tmp/sebrauc_reboot_test" + } + + return cfg +} + +// Get returns the configuration extracted from env variables or config file. +func Get() *Config { + return GetWithFlags("", 0) +} diff --git a/src/config/config_test.go b/src/config/config_test.go new file mode 100644 index 0000000..5725874 --- /dev/null +++ b/src/config/config_test.go @@ -0,0 +1,150 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures" + "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" + "github.com/stretchr/testify/assert" +) + +func TestDefault(t *testing.T) { + mode.Set(mode.Prod) + defer mode.Set(mode.Dev) + + cfg := Get() + + assert.Equal(t, "", cfg.Server.Address) + assert.Equal(t, 80, cfg.Server.Port) + + assert.Equal(t, 45, cfg.Server.Websocket.Ping) + assert.Equal(t, 15, cfg.Server.Websocket.Timeout) + + assert.Equal(t, "", cfg.Server.Compression.Gzip) + assert.Equal(t, "", cfg.Server.Compression.Brotli) + + assert.Equal(t, "/tmp", cfg.Tmpdir) + + assert.Equal(t, "/etc/os-release", cfg.Sysinfo.ReleaseFile) + assert.Equal(t, "NAME", cfg.Sysinfo.NameKey) + assert.Equal(t, "VERSION", cfg.Sysinfo.VersionKey) + assert.Equal(t, "/etc/hostname", cfg.Sysinfo.HostnameFile) + assert.Equal(t, "/proc/uptime", cfg.Sysinfo.UptimeFile) + + assert.Equal(t, "rauc status --output-format=json", cfg.Commands.RaucStatus) + assert.Equal(t, "rauc install", cfg.Commands.RaucInstall) + assert.Equal(t, "shutdown -r 0", cfg.Commands.Reboot) +} + +func TestConfigFile(t *testing.T) { + mode.Set(mode.Prod) + defer mode.Set(mode.Dev) + + cfile := filepath.Join(fixtures.GetTestfilesDir(), "sebrauc.toml") + cfg := GetWithFlags(cfile, 0) + + assert.Equal(t, "127.0.0.1", cfg.Server.Address) + assert.Equal(t, 8001, cfg.Server.Port) + + assert.Equal(t, 30, cfg.Server.Websocket.Ping) + assert.Equal(t, 10, cfg.Server.Websocket.Timeout) + + assert.Equal(t, "max", cfg.Server.Compression.Gzip) + assert.Equal(t, "false", cfg.Server.Compression.Brotli) + + assert.Equal(t, "/var/tmp", cfg.Tmpdir) + + assert.Equal(t, "/etc/release", cfg.Sysinfo.ReleaseFile) + assert.Equal(t, "PRETTY_NAME", cfg.Sysinfo.NameKey) + assert.Equal(t, "VER", cfg.Sysinfo.VersionKey) + assert.Equal(t, "/etc/hn", cfg.Sysinfo.HostnameFile) + assert.Equal(t, "/proc/up", cfg.Sysinfo.UptimeFile) + + assert.Equal(t, "myrauc status --output-format=json", cfg.Commands.RaucStatus) + assert.Equal(t, "myrauc install", cfg.Commands.RaucInstall) + assert.Equal(t, "reboot", cfg.Commands.Reboot) +} + +func TestEnvvar(t *testing.T) { + mode.Set(mode.Prod) + defer mode.Set(mode.Dev) + + os.Clearenv() + defer os.Clearenv() + + os.Setenv("SEBRAUC_TMPDIR", "/var/tmp") + os.Setenv("SEBRAUC_SERVER_ADDRESS", "127.0.0.1") + os.Setenv("SEBRAUC_SERVER_PORT", "8001") + os.Setenv("SEBRAUC_SERVER_WEBSOCKET_PING", "30") + os.Setenv("SEBRAUC_SERVER_WEBSOCKET_TIMEOUT", "10") + os.Setenv("SEBRAUC_SERVER_COMPRESSION_GZIP", "max") + os.Setenv("SEBRAUC_SERVER_COMPRESSION_BROTLI", "false") + os.Setenv("SEBRAUC_SYSINFO_RELEASEFILE", "/etc/release") + os.Setenv("SEBRAUC_SYSINFO_NAMEKEY", "PRETTY_NAME") + os.Setenv("SEBRAUC_SYSINFO_VERSIONKEY", "VER") + os.Setenv("SEBRAUC_SYSINFO_HOSTNAMEFILE", "/etc/hn") + os.Setenv("SEBRAUC_SYSINFO_UPTIMEFILE", "/proc/up") + os.Setenv("SEBRAUC_COMMANDS_RAUCSTATUS", "myrauc status --output-format=json") + os.Setenv("SEBRAUC_COMMANDS_RAUCINSTALL", "myrauc install") + os.Setenv("SEBRAUC_COMMANDS_REBOOT", "reboot") + + cfg := Get() + + assert.Equal(t, "127.0.0.1", cfg.Server.Address) + assert.Equal(t, 8001, cfg.Server.Port) + + assert.Equal(t, 30, cfg.Server.Websocket.Ping) + assert.Equal(t, 10, cfg.Server.Websocket.Timeout) + + assert.Equal(t, "max", cfg.Server.Compression.Gzip) + assert.Equal(t, "false", cfg.Server.Compression.Brotli) + + assert.Equal(t, "/var/tmp", cfg.Tmpdir) + + assert.Equal(t, "/etc/release", cfg.Sysinfo.ReleaseFile) + assert.Equal(t, "PRETTY_NAME", cfg.Sysinfo.NameKey) + assert.Equal(t, "VER", cfg.Sysinfo.VersionKey) + assert.Equal(t, "/etc/hn", cfg.Sysinfo.HostnameFile) + assert.Equal(t, "/proc/up", cfg.Sysinfo.UptimeFile) + + assert.Equal(t, "myrauc status --output-format=json", cfg.Commands.RaucStatus) + assert.Equal(t, "myrauc install", cfg.Commands.RaucInstall) + assert.Equal(t, "reboot", cfg.Commands.Reboot) +} + +func TestDevMode(t *testing.T) { + cfg := Get() + + //nolint:lll + assert.Equal(t, "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock status --output-format=json", cfg.Commands.RaucStatus) + //nolint:lll + assert.Equal(t, "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock install", cfg.Commands.RaucInstall) + assert.Equal(t, "touch /tmp/sebrauc_reboot_test", cfg.Commands.Reboot) +} + +func TestFlags(t *testing.T) { + cfg := GetWithFlags("", 8001) + + assert.Equal(t, 8001, cfg.Server.Port) +} + +func TestStripTrailingSlashes(t *testing.T) { + tests := []struct { + in string + out string + }{ + {in: "/tmp", out: "/tmp"}, + {in: "/tmp/", out: "/tmp"}, + {in: "/tmp///", out: "/tmp"}, + } + + for i, tt := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + res := stripTrailingSlashes(tt.in) + assert.Equal(t, tt.out, res) + }) + } +} diff --git a/src/fixtures/testfiles/sebrauc.toml b/src/fixtures/testfiles/sebrauc.toml new file mode 100644 index 0000000..c806f96 --- /dev/null +++ b/src/fixtures/testfiles/sebrauc.toml @@ -0,0 +1,28 @@ +# SEBRAUC config file for testing +# Dont use for real, the commands and paths are not correct + +Tmpdir = "/var/tmp/" + +[Server] +Address = "127.0.0.1" +Port = 8001 + +[Server.Websocket] +Ping = 30 +Timeout = 10 + +[Server.Compression] +Gzip = "max" +Brotli = "false" + +[Sysinfo] +ReleaseFile = "/etc/release" +NameKey = "PRETTY_NAME" +VersionKey = "VER" +HostnameFile = "/etc/hn" +UptimeFile = "/proc/up" + +[Commands] +RaucStatus = "myrauc status --output-format=json" +RaucInstall = "myrauc install" +Reboot = "reboot" diff --git a/src/server/stream/stream_test.go b/src/server/stream/stream_test.go index ca24cca..15de1f5 100644 --- a/src/server/stream/stream_test.go +++ b/src/server/stream/stream_test.go @@ -18,6 +18,7 @@ import ( func TestFailureOnNormalHttpRequest(t *testing.T) { mode.Set(mode.TestDev) + defer mode.Set(mode.Dev) defer leaktest.Check(t)() @@ -33,6 +34,8 @@ func TestFailureOnNormalHttpRequest(t *testing.T) { func TestWriteMessageFails(t *testing.T) { mode.Set(mode.TestDev) + defer mode.Set(mode.Dev) + oldWrite := writeBytes // try emulate an write error, mostly this should kill the ReadMessage // goroutine first but you'll never know. @@ -62,6 +65,8 @@ func TestWriteMessageFails(t *testing.T) { func TestWritePingFails(t *testing.T) { mode.Set(mode.TestDev) + defer mode.Set(mode.Dev) + oldPing := ping // try emulate an write error, mostly this should kill the ReadMessage // gorouting first but you'll never know. @@ -96,6 +101,7 @@ func TestWritePingFails(t *testing.T) { func TestPing(t *testing.T) { mode.Set(mode.TestDev) + defer mode.Set(mode.Dev) server, api := bootTestServer() defer server.Close() @@ -132,6 +138,7 @@ func TestPing(t *testing.T) { func TestCloseClientOnNotReading(t *testing.T) { mode.Set(mode.TestDev) + defer mode.Set(mode.Dev) server, api := bootTestServer() defer server.Close() @@ -155,6 +162,8 @@ func TestCloseClientOnNotReading(t *testing.T) { func TestMessageDirectlyAfterConnect(t *testing.T) { mode.Set(mode.Prod) + defer mode.Set(mode.Dev) + defer leaktest.Check(t)() server, api := bootTestServer() defer server.Close() @@ -172,6 +181,8 @@ func TestMessageDirectlyAfterConnect(t *testing.T) { func TestDeleteClientShouldCloseConnection(t *testing.T) { mode.Set(mode.Prod) + defer mode.Set(mode.Dev) + defer leaktest.Check(t)() server, api := bootTestServer() defer server.Close() @@ -194,6 +205,7 @@ func TestDeleteClientShouldCloseConnection(t *testing.T) { func TestNotify(t *testing.T) { mode.Set(mode.TestDev) + defer mode.Set(mode.Dev) defer leaktest.Check(t)() server, api := bootTestServer() @@ -271,6 +283,8 @@ func TestBroadcast(t *testing.T) { func Test_sameOrigin_returnsTrue(t *testing.T) { mode.Set(mode.Prod) + defer mode.Set(mode.Dev) + req := httptest.NewRequest("GET", "http://example.com/stream", nil) req.Header.Set("Origin", "http://example.com") actual := isAllowedOrigin(req, nil) @@ -279,6 +293,7 @@ func Test_sameOrigin_returnsTrue(t *testing.T) { func Test_sameOrigin_returnsTrue_withCustomPort(t *testing.T) { mode.Set(mode.Prod) + defer mode.Set(mode.Dev) req := httptest.NewRequest("GET", "http://example.com:8080/stream", nil) req.Header.Set("Origin", "http://example.com:8080") actual := isAllowedOrigin(req, nil) @@ -287,6 +302,8 @@ func Test_sameOrigin_returnsTrue_withCustomPort(t *testing.T) { func Test_isAllowedOrigin_withoutAllowedOrigins_failsWhenNotSameOrigin(t *testing.T) { mode.Set(mode.Prod) + defer mode.Set(mode.Dev) + req := httptest.NewRequest("GET", "http://example.com/stream", nil) req.Header.Set("Origin", "http://gorify.example.com") actual := isAllowedOrigin(req, nil) @@ -295,6 +312,8 @@ func Test_isAllowedOrigin_withoutAllowedOrigins_failsWhenNotSameOrigin(t *testin func Test_isAllowedOriginMatching(t *testing.T) { mode.Set(mode.Prod) + defer mode.Set(mode.Dev) + compiledAllowedOrigins := compileAllowedWebSocketOrigins( []string{"go.{4}\\.example\\.com", "go\\.example\\.com"}, ) @@ -312,6 +331,8 @@ func Test_isAllowedOriginMatching(t *testing.T) { func Test_emptyOrigin_returnsTrue(t *testing.T) { mode.Set(mode.Prod) + defer mode.Set(mode.Dev) + req := httptest.NewRequest("GET", "http://example.com/stream", nil) actual := isAllowedOrigin(req, nil) assert.True(t, actual) @@ -319,6 +340,8 @@ func Test_emptyOrigin_returnsTrue(t *testing.T) { func Test_otherOrigin_returnsFalse(t *testing.T) { mode.Set(mode.Prod) + defer mode.Set(mode.Dev) + req := httptest.NewRequest("GET", "http://example.com/stream", nil) req.Header.Set("Origin", "http://otherexample.de") actual := isAllowedOrigin(req, nil) @@ -327,6 +350,8 @@ func Test_otherOrigin_returnsFalse(t *testing.T) { func Test_invalidOrigin_returnsFalse(t *testing.T) { mode.Set(mode.Prod) + defer mode.Set(mode.Dev) + req := httptest.NewRequest("GET", "http://example.com/stream", nil) req.Header.Set("Origin", "http\\://otherexample.de") actual := isAllowedOrigin(req, nil) From acd1db7363cbc2ba18409dfeae1f6a4db2595546 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 24 Dec 2021 01:55:16 +0100 Subject: [PATCH 18/27] integrated config fixed method type for /reboot in apidoc --- .gitignore | 1 + src/config/config.go | 18 +++---- src/fixtures/testcmd/testcmd.go | 8 ++++ src/main.go | 19 +++++--- src/rauc/rauc.go | 16 +++++-- src/rauc/rauc_test.go | 5 +- src/server/middleware/compression.go | 14 ++++++ src/server/server.go | 72 ++++++++++++++++------------ src/server/swagger/swagger.go | 2 +- src/server/swagger/swagger.yaml | 2 +- src/sysinfo/sysinfo.go | 61 ++++++++++++++++------- src/sysinfo/sysinfo_test.go | 10 +++- src/util/commands.go | 13 ----- src/util/commands_mock.go | 13 ----- src/util/mode/mode.go | 4 ++ src/util/mode/mode_dev.go | 6 +++ src/util/mode/mode_prod.go | 6 +++ src/util/util.go | 8 ++-- src/util/util_test.go | 5 +- src/util/version.go | 6 --- ui/src/sebrauc-client/api.ts | 2 +- ui/ui.go | 11 ++--- ui/ui_test.go | 2 +- 23 files changed, 183 insertions(+), 121 deletions(-) create mode 100644 src/fixtures/testcmd/testcmd.go create mode 100644 src/server/middleware/compression.go delete mode 100644 src/util/commands.go delete mode 100644 src/util/commands_mock.go create mode 100644 src/util/mode/mode_dev.go create mode 100644 src/util/mode/mode_prod.go diff --git a/.gitignore b/.gitignore index d59a62b..1c26218 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build tmp +sebrauc.toml diff --git a/src/config/config.go b/src/config/config.go index 5a7d0a9..f13ca3d 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -3,6 +3,7 @@ package config import ( "strings" + "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/testcmd" "code.thetadev.de/TSGRain/SEBRAUC/src/util" "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" "github.com/jinzhu/configor" @@ -86,11 +87,12 @@ func stripTrailingSlashes(pathIn string) string { // env variables or config file. func GetWithFlags(pathIn string, portIn int) *Config { cfg := new(Config) - err := configor.New(&configor.Config{ - // Debug: true, - // Verbose: true, + cfgor := configor.New(&configor.Config{ + // Debug: true, ENVPrefix: "SEBRAUC", - }).Load(cfg, findConfigFile(pathIn)) + }) + + err := cfgor.Load(cfg, findConfigFile(pathIn)) if err != nil { panic(err) } @@ -104,11 +106,9 @@ func GetWithFlags(pathIn string, portIn int) *Config { // Override commands with testing options if mode.IsDev() { - //nolint:lll - cfg.Commands.RaucStatus = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock status --output-format=json" - //nolint:lll - cfg.Commands.RaucInstall = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock install" - cfg.Commands.Reboot = "touch /tmp/sebrauc_reboot_test" + cfg.Commands.RaucStatus = testcmd.RaucStatus + cfg.Commands.RaucInstall = testcmd.RaucInstall + cfg.Commands.Reboot = testcmd.Reboot } return cfg diff --git a/src/fixtures/testcmd/testcmd.go b/src/fixtures/testcmd/testcmd.go new file mode 100644 index 0000000..b157bea --- /dev/null +++ b/src/fixtures/testcmd/testcmd.go @@ -0,0 +1,8 @@ +package testcmd + +//nolint:lll +const ( + RaucStatus = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock status --output-format=json" + RaucInstall = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock install" + Reboot = "touch /tmp/sebrauc_reboot_test" +) diff --git a/src/main.go b/src/main.go index 8cbc312..4a6a273 100644 --- a/src/main.go +++ b/src/main.go @@ -3,27 +3,32 @@ package main import ( "fmt" "log" - "time" - "code.thetadev.de/TSGRain/SEBRAUC/src/rauc" + "code.thetadev.de/TSGRain/SEBRAUC/src/config" "code.thetadev.de/TSGRain/SEBRAUC/src/server" - "code.thetadev.de/TSGRain/SEBRAUC/src/server/stream" "code.thetadev.de/TSGRain/SEBRAUC/src/util" "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" ) +const titleArt = ` _____ __________ ____ ___ __ ________ + / ___// ____/ __ )/ __ \/ | / / / / ____/ + \__ \/ __/ / __ / /_/ / /| |/ / / / / + ___/ / /___/ /_/ / _, _/ ___ / /_/ / /___ +/____/_____/_____/_/ |_/_/ |_\____/\____/ ` + func main() { - fmt.Println("SEBRAUC " + util.Version()) + fmt.Println(titleArt + util.Version() + "\n") if mode.IsDev() { fmt.Println("Test mode active - no update operations are executed.") fmt.Println("Build with -tags prod to enable live mode.") } - streamer := stream.New(10*time.Second, 1*time.Second, []string{}) - raucUpdater := &rauc.Rauc{} + cfg := config.GetWithFlags("", 8080) - srv := server.NewServer(":8080", streamer, raucUpdater) + fmt.Printf("Starting server at %s:%d\n", cfg.Server.Address, cfg.Server.Port) + + srv := server.NewServer(cfg) err := srv.Run() if err != nil { log.Fatalln(err) diff --git a/src/rauc/rauc.go b/src/rauc/rauc.go index 691225c..6254e0b 100644 --- a/src/rauc/rauc.go +++ b/src/rauc/rauc.go @@ -19,9 +19,16 @@ var ( ) type Rauc struct { - bc util.Broadcaster - status model.RaucStatus - runningMtx sync.Mutex + cmdRaucInstall string + bc util.Broadcaster + status model.RaucStatus + runningMtx sync.Mutex +} + +func New(cmdRaucInstall string) *Rauc { + return &Rauc{ + cmdRaucInstall: cmdRaucInstall, + } } func (r *Rauc) SetBroadcaster(bc util.Broadcaster) { @@ -58,8 +65,7 @@ func (r *Rauc) RunRauc(updateFile string) error { } r.bcStatus() - cmd := util.CommandFromString( - fmt.Sprintf("%s install %s", util.RaucCmd, updateFile)) + cmd := util.CommandFromString(r.cmdRaucInstall + " " + updateFile) readPipe, _ := cmd.StdoutPipe() cmd.Stderr = cmd.Stdout diff --git a/src/rauc/rauc_test.go b/src/rauc/rauc_test.go index df11109..9f5b6a3 100644 --- a/src/rauc/rauc_test.go +++ b/src/rauc/rauc_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/testcmd" "code.thetadev.de/TSGRain/SEBRAUC/src/util" "github.com/stretchr/testify/assert" ) @@ -75,7 +76,7 @@ func TestRauc(t *testing.T) { os.Setenv("RAUC_MOCK_TEST", "1") os.Setenv("RAUC_MOCK_FAIL", tt.fail) - updater := &Rauc{} + updater := New(testcmd.RaucInstall) bc := &broadcasterMock{} updater.SetBroadcaster(bc) @@ -101,7 +102,7 @@ func TestRauc(t *testing.T) { } func createTmpfile() string { - tmpdir, err := util.GetTmpdir() + tmpdir, err := util.GetTmpdir("/tmp") if err != nil { panic(err) } diff --git a/src/server/middleware/compression.go b/src/server/middleware/compression.go new file mode 100644 index 0000000..0fb0100 --- /dev/null +++ b/src/server/middleware/compression.go @@ -0,0 +1,14 @@ +package middleware + +import ( + "code.thetadev.de/TSGRain/ginzip" + "github.com/gin-gonic/gin" +) + +func Compression(gzip, brotli string) gin.HandlerFunc { + opts := ginzip.DefaultOptions() + opts.GzipLevel = gzip + opts.BrotliLevel = brotli + + return ginzip.New(opts) +} diff --git a/src/server/server.go b/src/server/server.go index 74a2815..7525992 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -16,8 +16,11 @@ import ( "net/http" "time" + "code.thetadev.de/TSGRain/SEBRAUC/src/config" "code.thetadev.de/TSGRain/SEBRAUC/src/model" + "code.thetadev.de/TSGRain/SEBRAUC/src/rauc" "code.thetadev.de/TSGRain/SEBRAUC/src/server/middleware" + "code.thetadev.de/TSGRain/SEBRAUC/src/server/stream" "code.thetadev.de/TSGRain/SEBRAUC/src/server/swagger" "code.thetadev.de/TSGRain/SEBRAUC/src/sysinfo" "code.thetadev.de/TSGRain/SEBRAUC/src/util" @@ -28,38 +31,42 @@ import ( "github.com/google/uuid" ) -type serverStreamer interface { - util.Broadcaster - Handle(ctx *gin.Context) - Close() -} - -type serverUpdater interface { - GetStatus() model.RaucStatus - RunRauc(updateFile string) error - SetBroadcaster(bc util.Broadcaster) -} - type SEBRAUCServer struct { - address string - streamer serverStreamer - udater serverUpdater + config *config.Config + streamer *stream.API + updater *rauc.Rauc + sysinfo *sysinfo.Sysinfo tmpdir string } -func NewServer(address string, streamer serverStreamer, - updater serverUpdater) *SEBRAUCServer { +func NewServer(config *config.Config) *SEBRAUCServer { + updater := rauc.New(config.Commands.RaucInstall) + streamer := stream.New( + time.Duration(config.Server.Websocket.Ping)*time.Second, + time.Duration(config.Server.Websocket.Timeout)*time.Second, + []string{}, + ) + sysinfo := sysinfo.New( + config.Commands.RaucStatus, + config.Sysinfo.ReleaseFile, + config.Sysinfo.NameKey, + config.Sysinfo.VersionKey, + config.Sysinfo.HostnameFile, + config.Sysinfo.UptimeFile, + ) + updater.SetBroadcaster(streamer) - tmpdir, err := util.GetTmpdir() + tmpdir, err := util.GetTmpdir(config.Tmpdir) if err != nil { panic(err) } return &SEBRAUCServer{ - address: address, - udater: updater, + config: config, + updater: updater, streamer: streamer, + sysinfo: sysinfo, tmpdir: tmpdir, } } @@ -79,7 +86,7 @@ func (srv *SEBRAUCServer) Run() error { api := router.Group("/api", middleware.ErrorHandler(true), middleware.PanicHandler(true)) - // ROUTES + // API ROUTES api.GET("/ws", srv.streamer.Handle) api.GET("/status", srv.controllerStatus) api.GET("/info", srv.controllerInfo) @@ -96,11 +103,16 @@ func (srv *SEBRAUCServer) Run() error { api.GET("/panic", srv.controllerPanic) } - ui.Register(router) + // UI + uiGroup := router.Group("/", middleware.Compression( + srv.config.Server.Compression.Gzip, + srv.config.Server.Compression.Brotli), + ) + ui.Register(uiGroup) + swagger.Register(uiGroup) - swagger.Register(router) - - return router.Run(srv.address) + return router.Run(fmt.Sprintf("%s:%d", + srv.config.Server.Address, srv.config.Server.Port)) } // swagger:operation POST /update startUpdate @@ -151,7 +163,7 @@ func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) { return } - err = srv.udater.RunRauc(updateFile) + err = srv.updater.RunRauc(updateFile) if err == nil { writeStatus(c, true, "Update started") } else { @@ -176,7 +188,7 @@ func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) { // schema: // $ref: "#/definitions/Error" func (srv *SEBRAUCServer) controllerStatus(c *gin.Context) { - c.JSON(http.StatusOK, srv.udater.GetStatus()) + c.JSON(http.StatusOK, srv.updater.GetStatus()) } // swagger:operation GET /info getInfo @@ -195,7 +207,7 @@ func (srv *SEBRAUCServer) controllerStatus(c *gin.Context) { // schema: // $ref: "#/definitions/Error" func (srv *SEBRAUCServer) controllerInfo(c *gin.Context) { - info, err := sysinfo.GetSysinfo() + info, err := srv.sysinfo.GetSysinfo() if err != nil { c.Error(err) } else { @@ -203,7 +215,7 @@ func (srv *SEBRAUCServer) controllerInfo(c *gin.Context) { } } -// swagger:operation GET /reboot startReboot +// swagger:operation POST /reboot startReboot // // Reboot the system // @@ -219,7 +231,7 @@ func (srv *SEBRAUCServer) controllerInfo(c *gin.Context) { // schema: // $ref: "#/definitions/Error" func (srv *SEBRAUCServer) controllerReboot(c *gin.Context) { - go util.Reboot(5 * time.Second) + go util.Reboot(srv.config.Commands.Reboot, 5*time.Second) writeStatus(c, true, "System is rebooting") } diff --git a/src/server/swagger/swagger.go b/src/server/swagger/swagger.go index e48ca00..733b372 100644 --- a/src/server/swagger/swagger.go +++ b/src/server/swagger/swagger.go @@ -13,7 +13,7 @@ var swaggerHtml []byte //go:embed swagger.yaml var swaggerYaml []byte -func Register(r *gin.Engine) { +func Register(r gin.IRouter) { swg := r.Group("/api/swagger", middleware.Cache) swg.GET("/", func(c *gin.Context) { diff --git a/src/server/swagger/swagger.yaml b/src/server/swagger/swagger.yaml index fb137ff..e21f243 100644 --- a/src/server/swagger/swagger.yaml +++ b/src/server/swagger/swagger.yaml @@ -166,7 +166,7 @@ paths: schema: $ref: "#/definitions/Error" /reboot: - get: + post: description: Reboot the system operationId: startReboot produces: diff --git a/src/sysinfo/sysinfo.go b/src/sysinfo/sysinfo.go index 9442977..99f0642 100644 --- a/src/sysinfo/sysinfo.go +++ b/src/sysinfo/sysinfo.go @@ -11,6 +11,15 @@ import ( "code.thetadev.de/TSGRain/SEBRAUC/src/util" ) +type Sysinfo struct { + cmdRaucStatus string + releaseFile string + hostnameFile string + uptimeFile string + rexpName *regexp.Regexp + rexpVersion *regexp.Regexp +} + type raucInfo struct { Compatible string `json:"compatible"` Variant string `json:"variant"` @@ -34,11 +43,27 @@ type osRelease struct { OsVersion string `json:"os_version"` } -var ( - rexpOsName = regexp.MustCompile(`(?m)^NAME="(.+)"`) - rexpOsVersion = regexp.MustCompile(`(?m)^VERSION="(.+)"`) - rexpUptime = regexp.MustCompile(`^\d+`) -) +var rexpUptime = regexp.MustCompile(`^\d+`) + +func New(cmdRaucStatus string, releaseFile string, nameKey string, versionKey string, + hostnameFile string, uptimeFile string) *Sysinfo { + + return &Sysinfo{ + cmdRaucStatus: cmdRaucStatus, + releaseFile: releaseFile, + hostnameFile: hostnameFile, + uptimeFile: uptimeFile, + rexpName: regexp.MustCompile( + `(?m)^` + regexp.QuoteMeta(nameKey) + `="(.+)"`), + rexpVersion: regexp.MustCompile( + `(?m)^` + regexp.QuoteMeta(versionKey) + `="(.+)"`), + } +} + +func Default(cmdRaucStatus string) *Sysinfo { + return New(cmdRaucStatus, "/etc/os-release", "NAME", "VERSION", + "/etc/hostname", "/proc/uptime") +} func parseRaucInfo(raucInfoJson []byte) (raucInfo, error) { res := raucInfo{} @@ -46,14 +71,14 @@ func parseRaucInfo(raucInfoJson []byte) (raucInfo, error) { return res, err } -func parseOsRelease(osReleaseFile string) (osRelease, error) { - osReleaseTxt, err := os.ReadFile(osReleaseFile) +func (s *Sysinfo) parseOsRelease() (osRelease, error) { + osReleaseTxt, err := os.ReadFile(s.releaseFile) if err != nil { return osRelease{}, err } - nameMatch := rexpOsName.FindSubmatch(osReleaseTxt) - versionMatch := rexpOsVersion.FindSubmatch(osReleaseTxt) + nameMatch := s.rexpName.FindSubmatch(osReleaseTxt) + versionMatch := s.rexpVersion.FindSubmatch(osReleaseTxt) name := "" if nameMatch != nil { @@ -116,8 +141,8 @@ func mapSysinfo(rinf raucInfo, osr osRelease, uptime int, } } -func getUptime() (int, error) { - uptimeRaw, err := os.ReadFile("/proc/uptime") +func (s *Sysinfo) getUptime() (int, error) { + uptimeRaw, err := os.ReadFile(s.uptimeFile) if err != nil { return 0, err } @@ -126,16 +151,16 @@ func getUptime() (int, error) { return strconv.Atoi(string(uptimeChars)) } -func getHostname() string { - hostname, err := os.ReadFile("/etc/hostname") +func (s *Sysinfo) getHostname() string { + hostname, err := os.ReadFile(s.hostnameFile) if err != nil { return "" } return strings.TrimSpace(string(hostname)) } -func GetSysinfo() (model.SystemInfo, error) { - cmd := util.CommandFromString(util.RaucCmd + " status --output-format=json") +func (s *Sysinfo) GetSysinfo() (model.SystemInfo, error) { + cmd := util.CommandFromString(s.cmdRaucStatus) rinfJson, err := cmd.Output() if err != nil { return model.SystemInfo{}, err @@ -146,17 +171,17 @@ func GetSysinfo() (model.SystemInfo, error) { return model.SystemInfo{}, err } - osinf, err := parseOsRelease("/etc/os-release") + osinf, err := s.parseOsRelease() if err != nil { return model.SystemInfo{}, err } - uptime, err := getUptime() + uptime, err := s.getUptime() if err != nil { return model.SystemInfo{}, err } - hostname := getHostname() + hostname := s.getHostname() return mapSysinfo(rinf, osinf, uptime, hostname), nil } diff --git a/src/sysinfo/sysinfo_test.go b/src/sysinfo/sysinfo_test.go index 1e6e86b..575502e 100644 --- a/src/sysinfo/sysinfo_test.go +++ b/src/sysinfo/sysinfo_test.go @@ -5,6 +5,7 @@ import ( "testing" "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures" + "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/testcmd" "code.thetadev.de/TSGRain/SEBRAUC/src/model" "github.com/stretchr/testify/assert" ) @@ -83,7 +84,10 @@ func TestParseOsRelease(t *testing.T) { testfiles := fixtures.GetTestfilesDir() osReleaseFile := filepath.Join(testfiles, "os-release") - osRel, err := parseOsRelease(osReleaseFile) + si := New(testcmd.RaucStatus, osReleaseFile, "NAME", "VERSION", + "/etc/hostname", "/proc/uptime") + + osRel, err := si.parseOsRelease() if err != nil { panic(err) } @@ -111,7 +115,9 @@ func TestGetFSNameFromBootname(t *testing.T) { } func TestGetSysinfo(t *testing.T) { - sysinfo, err := GetSysinfo() + si := Default(testcmd.RaucStatus) + + sysinfo, err := si.GetSysinfo() if err != nil { panic(err) } diff --git a/src/util/commands.go b/src/util/commands.go deleted file mode 100644 index 3341b90..0000000 --- a/src/util/commands.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build prod -// +build prod - -package util - -import "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" - -const ( - RebootCmd = "shutdown -r 0" - RaucCmd = "rauc" - - appmode = mode.Prod -) diff --git a/src/util/commands_mock.go b/src/util/commands_mock.go deleted file mode 100644 index f52d17d..0000000 --- a/src/util/commands_mock.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build !prod -// +build !prod - -package util - -import "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" - -const ( - RebootCmd = "touch /tmp/sebrauc_reboot_test" - RaucCmd = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock" - - appmode = mode.Dev -) diff --git a/src/util/mode/mode.go b/src/util/mode/mode.go index c8625f6..6b0a3c8 100644 --- a/src/util/mode/mode.go +++ b/src/util/mode/mode.go @@ -13,6 +13,10 @@ const ( var currentMode = Dev +func init() { + Set(appmode) +} + func Set(newMode string) { currentMode = newMode updateGinMode() diff --git a/src/util/mode/mode_dev.go b/src/util/mode/mode_dev.go new file mode 100644 index 0000000..71cb6cd --- /dev/null +++ b/src/util/mode/mode_dev.go @@ -0,0 +1,6 @@ +//go:build !prod +// +build !prod + +package mode + +const appmode = Dev diff --git a/src/util/mode/mode_prod.go b/src/util/mode/mode_prod.go new file mode 100644 index 0000000..571398d --- /dev/null +++ b/src/util/mode/mode_prod.go @@ -0,0 +1,6 @@ +//go:build prod +// +build prod + +package mode + +const appmode = Prod diff --git a/src/util/util.go b/src/util/util.go index a64d409..2d94270 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -25,8 +25,8 @@ func CreateDirIfNotExists(dirpath string) error { return nil } -func GetTmpdir() (string, error) { - tmpdir := filepath.Join(os.TempDir(), tmpdirName) +func GetTmpdir(root string) (string, error) { + tmpdir := filepath.Join(root, tmpdirName) err := CreateDirIfNotExists(tmpdir) return tmpdir, err } @@ -41,8 +41,8 @@ func CommandFromString(cmdString string) *exec.Cmd { return exec.Command(parts[0], parts[1:]...) } -func Reboot(t time.Duration) { +func Reboot(rebootCmd string, t time.Duration) { time.Sleep(t) - cmd := CommandFromString(RebootCmd) + cmd := CommandFromString(rebootCmd) _ = cmd.Run() } diff --git a/src/util/util_test.go b/src/util/util_test.go index eb28203..cd056bb 100644 --- a/src/util/util_test.go +++ b/src/util/util_test.go @@ -6,6 +6,7 @@ import ( "testing" "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures" + "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/testcmd" "github.com/stretchr/testify/assert" ) @@ -41,7 +42,7 @@ func TestDoesFileExist(t *testing.T) { } func TestTmpdir(t *testing.T) { - td, err := GetTmpdir() + td, err := GetTmpdir("/tmp") if err != nil { panic(err) } @@ -103,7 +104,7 @@ func TestReboot(t *testing.T) { testfile := "/tmp/sebrauc_reboot_test" _ = os.Remove(testfile) - Reboot(0) + Reboot(testcmd.Reboot, 0) assert.FileExists(t, testfile) } diff --git a/src/util/version.go b/src/util/version.go index e9a1869..7e8dba4 100644 --- a/src/util/version.go +++ b/src/util/version.go @@ -1,13 +1,7 @@ package util -import "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode" - var version = "dev" -func init() { - mode.Set(appmode) -} - func Version() string { return version } diff --git a/ui/src/sebrauc-client/api.ts b/ui/src/sebrauc-client/api.ts index c98ab0a..b2106ea 100644 --- a/ui/src/sebrauc-client/api.ts +++ b/ui/src/sebrauc-client/api.ts @@ -295,7 +295,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati baseOptions = configuration.baseOptions } - const localVarRequestOptions = {method: "GET", ...baseOptions, ...options} + const localVarRequestOptions = {method: "POST", ...baseOptions, ...options} const localVarHeaderParameter = {} as any const localVarQueryParameter = {} as any diff --git a/ui/ui.go b/ui/ui.go index 66329e8..d66f422 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -9,7 +9,6 @@ import ( "code.thetadev.de/TSGRain/SEBRAUC/src/server/middleware" "code.thetadev.de/TSGRain/SEBRAUC/src/util" - "code.thetadev.de/TSGRain/ginzip" "github.com/gin-gonic/gin" ) @@ -34,15 +33,15 @@ func distFS() fs.FS { return subFS(assets, distDir) } -func Register(r *gin.Engine) { +func Register(r gin.IRouter) { indexHandler := getIndexHandler() - ui := r.Group("/", ginzip.New(ginzip.DefaultOptions()), middleware.Cache) + uiAssets := r.Group("/assets", middleware.Cache) - ui.GET("/", indexHandler) - ui.GET("/index.html", indexHandler) + r.GET("/", indexHandler) + r.GET("/index.html", indexHandler) - ui.StaticFS("/assets", http.FS(subFS(distFS(), "assets"))) + uiAssets.StaticFS("/", http.FS(subFS(distFS(), "assets"))) } func getIndexHandler() gin.HandlerFunc { diff --git a/ui/ui_test.go b/ui/ui_test.go index acb0d46..be2a9a3 100644 --- a/ui/ui_test.go +++ b/ui/ui_test.go @@ -19,5 +19,5 @@ func TestUI(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "SEBRAUC") - assert.NotEmpty(t, w.Header().Get("Cache-Control")) + assert.Empty(t, w.Header().Get("Cache-Control")) } From 92bed651b76b4f138642e18f8e6519cee857f6f6 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Fri, 24 Dec 2021 02:14:13 +0100 Subject: [PATCH 19/27] add cmdline flags --- src/main.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main.go b/src/main.go index 4a6a273..3fcd902 100644 --- a/src/main.go +++ b/src/main.go @@ -1,8 +1,10 @@ package main import ( + "flag" "fmt" "log" + "os" "code.thetadev.de/TSGRain/SEBRAUC/src/config" "code.thetadev.de/TSGRain/SEBRAUC/src/server" @@ -17,14 +19,23 @@ const titleArt = ` _____ __________ ____ ___ __ ________ /____/_____/_____/_/ |_/_/ |_\____/\____/ ` func main() { + run(os.Args[1:]) +} + +func run(args []string) { fmt.Println(titleArt + util.Version() + "\n") + cmdFlags := flag.NewFlagSet("sebrauc", flag.ExitOnError) + port := cmdFlags.Int("p", 0, "HTTP port") + cfgPath := cmdFlags.String("c", "", "Config file path") + _ = cmdFlags.Parse(args) + if mode.IsDev() { fmt.Println("Test mode active - no update operations are executed.") fmt.Println("Build with -tags prod to enable live mode.") } - cfg := config.GetWithFlags("", 8080) + cfg := config.GetWithFlags(*cfgPath, *port) fmt.Printf("Starting server at %s:%d\n", cfg.Server.Address, cfg.Server.Port) From df78f37d86b505bcee7f874a7353ee1735a078a1 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 25 Dec 2021 18:02:59 +0100 Subject: [PATCH 20/27] add server_test --- sebrauc.example.toml | 6 +- src/config/config.go | 40 +++++--- src/config/config_test.go | 6 +- src/fixtures/testutil.go | 20 ++++ src/fixtures/testutil_test.go | 12 +++ src/rauc/rauc_test.go | 11 ++- src/server/server.go | 13 ++- src/server/server_test.go | 170 ++++++++++++++++++++++++++++++++++ src/util/util.go | 21 ++++- src/util/util_test.go | 53 +++++++---- ui/ui_test.go | 76 +++++++++++++-- 11 files changed, 371 insertions(+), 57 deletions(-) create mode 100644 src/server/server_test.go diff --git a/sebrauc.example.toml b/sebrauc.example.toml index 6ae00e1..0d4b5d2 100644 --- a/sebrauc.example.toml +++ b/sebrauc.example.toml @@ -1,8 +1,8 @@ # SEBRAUC config file example -# Root temporary directory -# Update packages are stored temporarily under Tmpdir/sebrauc/update-.raucb -Tmpdir = "/tmp" +# Temporary directory +# Update packages are stored temporarily under Tmpdir/update-.raucb +Tmpdir = "/tmp/sebrauc" # Webserver options [Server] diff --git a/src/config/config.go b/src/config/config.go index f13ca3d..d7a71f8 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -11,9 +11,9 @@ import ( // SEBRAUC config object type Config struct { - // Root temporary directory - // Update packages are stored temporarily under Tmpdir/sebrauc/update-.raucb - Tmpdir string `default:"/tmp"` + // Temporary directory + // Update packages are stored temporarily under Tmpdir/update-.raucb + Tmpdir string // Webserver options Server struct { @@ -83,27 +83,18 @@ func stripTrailingSlashes(pathIn string) string { return strings.TrimRight(pathIn, "/") } -// GetWithFlags returns the configuration extracted from cmdline args, -// env variables or config file. -func GetWithFlags(pathIn string, portIn int) *Config { +func loadConfig(cfgFile string) *Config { cfg := new(Config) cfgor := configor.New(&configor.Config{ // Debug: true, ENVPrefix: "SEBRAUC", }) - err := cfgor.Load(cfg, findConfigFile(pathIn)) + err := cfgor.Load(cfg, findConfigFile(cfgFile)) if err != nil { panic(err) } - cfg.Tmpdir = stripTrailingSlashes(cfg.Tmpdir) - - // Override port with cmdline flag if set - if portIn > 0 { - cfg.Server.Port = portIn - } - // Override commands with testing options if mode.IsDev() { cfg.Commands.RaucStatus = testcmd.RaucStatus @@ -111,10 +102,29 @@ func GetWithFlags(pathIn string, portIn int) *Config { cfg.Commands.Reboot = testcmd.Reboot } + cfg.Tmpdir = stripTrailingSlashes(cfg.Tmpdir) + + return cfg +} + +// GetWithFlags returns the configuration extracted from cmdline args, +// env variables or config file. +func GetWithFlags(pathIn string, portIn int) *Config { + cfg := loadConfig(findConfigFile(pathIn)) + + // Override port with cmdline flag if set + if portIn > 0 { + cfg.Server.Port = portIn + } + return cfg } // Get returns the configuration extracted from env variables or config file. func Get() *Config { - return GetWithFlags("", 0) + return loadConfig(findConfigFile("")) +} + +func GetDefault() *Config { + return loadConfig("") } diff --git a/src/config/config_test.go b/src/config/config_test.go index 5725874..434da20 100644 --- a/src/config/config_test.go +++ b/src/config/config_test.go @@ -26,7 +26,7 @@ func TestDefault(t *testing.T) { assert.Equal(t, "", cfg.Server.Compression.Gzip) assert.Equal(t, "", cfg.Server.Compression.Brotli) - assert.Equal(t, "/tmp", cfg.Tmpdir) + assert.Equal(t, "", cfg.Tmpdir) assert.Equal(t, "/etc/os-release", cfg.Sysinfo.ReleaseFile) assert.Equal(t, "NAME", cfg.Sysinfo.NameKey) @@ -72,8 +72,8 @@ func TestEnvvar(t *testing.T) { mode.Set(mode.Prod) defer mode.Set(mode.Dev) - os.Clearenv() - defer os.Clearenv() + fixtures.ResetEnv() + defer fixtures.ResetEnv() os.Setenv("SEBRAUC_TMPDIR", "/var/tmp") os.Setenv("SEBRAUC_SERVER_ADDRESS", "127.0.0.1") diff --git a/src/fixtures/testutil.go b/src/fixtures/testutil.go index ee2f352..624eb29 100644 --- a/src/fixtures/testutil.go +++ b/src/fixtures/testutil.go @@ -3,8 +3,11 @@ package fixtures import ( "os" "path/filepath" + "strings" ) +var envPrefixes = []string{"SEBRAUC", "RAUC_MOCK"} + func doesFileExist(filepath string) bool { _, err := os.Stat(filepath) return !os.IsNotExist(err) @@ -38,3 +41,20 @@ func GetTestfilesDir() string { CdProjectRoot() return filepath.Join("src", "fixtures", "testfiles") } + +func ResetEnv() { + for _, envvar := range os.Environ() { + split := strings.SplitN(envvar, "=", 2) + if len(split) != 2 { + continue + } + + key := split[0] + + for _, prefix := range envPrefixes { + if strings.HasPrefix(key, prefix) { + _ = os.Unsetenv(key) + } + } + } +} diff --git a/src/fixtures/testutil_test.go b/src/fixtures/testutil_test.go index 44b32ef..79941f3 100644 --- a/src/fixtures/testutil_test.go +++ b/src/fixtures/testutil_test.go @@ -35,3 +35,15 @@ func TestCdProjectRoot(t *testing.T) { CdProjectRoot() assert.True(t, doesFileExist("go.sum")) } + +func TestResetEnv(t *testing.T) { + os.Setenv("RAUC_MOCK_TEST", "1") + os.Setenv("SEBRAUC_PORT", "8001") + + ResetEnv() + + _, exists := os.LookupEnv("RAUC_MOCK_TEST") + assert.False(t, exists) + _, exists = os.LookupEnv("SEBRAUC_PORT") + assert.False(t, exists) +} diff --git a/src/rauc/rauc_test.go b/src/rauc/rauc_test.go index 9f5b6a3..19595cd 100644 --- a/src/rauc/rauc_test.go +++ b/src/rauc/rauc_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures" "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/testcmd" "code.thetadev.de/TSGRain/SEBRAUC/src/util" "github.com/stretchr/testify/assert" @@ -73,6 +74,9 @@ func TestRauc(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + fixtures.ResetEnv() + defer fixtures.ResetEnv() + os.Setenv("RAUC_MOCK_TEST", "1") os.Setenv("RAUC_MOCK_FAIL", tt.fail) @@ -102,13 +106,10 @@ func TestRauc(t *testing.T) { } func createTmpfile() string { - tmpdir, err := util.GetTmpdir("/tmp") - if err != nil { - panic(err) - } + tmpdir := util.GetTmpdir("") tmpfile := filepath.Join(tmpdir, "test.raucb") - _, err = os.Create(tmpfile) + _, err := os.Create(tmpfile) if err != nil { panic(err) } diff --git a/src/server/server.go b/src/server/server.go index 7525992..30b4dc3 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -57,10 +57,7 @@ func NewServer(config *config.Config) *SEBRAUCServer { updater.SetBroadcaster(streamer) - tmpdir, err := util.GetTmpdir(config.Tmpdir) - if err != nil { - panic(err) - } + tmpdir := util.GetTmpdir(config.Tmpdir) return &SEBRAUCServer{ config: config, @@ -71,7 +68,7 @@ func NewServer(config *config.Config) *SEBRAUCServer { } } -func (srv *SEBRAUCServer) Run() error { +func (srv *SEBRAUCServer) getRouter() *gin.Engine { router := gin.New() router.Use(gin.Logger()) _ = router.SetTrustedProxies(nil) @@ -111,6 +108,12 @@ func (srv *SEBRAUCServer) Run() error { ui.Register(uiGroup) swagger.Register(uiGroup) + return router +} + +func (srv *SEBRAUCServer) Run() error { + router := srv.getRouter() + return router.Run(fmt.Sprintf("%s:%d", srv.config.Server.Address, srv.config.Server.Port)) } diff --git a/src/server/server_test.go b/src/server/server_test.go new file mode 100644 index 0000000..7bb8c4a --- /dev/null +++ b/src/server/server_test.go @@ -0,0 +1,170 @@ +package server + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "regexp" + "testing" + "time" + + "code.thetadev.de/TSGRain/SEBRAUC/src/config" + "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures" + "code.thetadev.de/TSGRain/SEBRAUC/src/model" + "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +type testServer struct { + srv *SEBRAUCServer + router *gin.Engine +} + +func newTestServer() *testServer { + sebraucServer := NewServer(config.GetDefault()) + router := sebraucServer.getRouter() + + return &testServer{ + srv: sebraucServer, + router: router, + } +} + +func (srv *testServer) testRequest(t assert.TestingT, method string, url string, + body io.Reader, contentType string, +) *httptest.ResponseRecorder { + req, err := http.NewRequest(method, url, body) + req.Header.Set("Content-Type", contentType) + assert.Nil(t, err) + + w := httptest.NewRecorder() + srv.router.ServeHTTP(w, req) + + return w +} + +func TestUpdate(t *testing.T) { + fixtures.ResetEnv() + defer fixtures.ResetEnv() + util.RemoveTmpdir("") + + os.Setenv("RAUC_MOCK_TEST", "1") + + srv := newTestServer() + + updateContent := []byte("mock update file") + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("updateFile", "update.raucb") + assert.Nil(t, err) + _, err = part.Write(updateContent) + assert.Nil(t, err) + err = writer.Close() + assert.Nil(t, err) + + w := srv.testRequest(t, "POST", "/api/update", body, writer.FormDataContentType()) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, + `{"success":true,"msg":"Update started"}`, + w.Body.String(), + ) + + // Find update file + tmpdir := util.GetTmpdir("") + //nolint:lll + updateFileExp := regexp.MustCompile( + `update_[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\.raucb`) + updateFile := "" + + tmpdirObj, err := os.Open(tmpdir) + assert.Nil(t, err) + list, err := tmpdirObj.ReadDir(-1) + assert.Nil(t, err) + + for _, f := range list { + if updateFileExp.MatchString(f.Name()) { + updateFile = filepath.Join(tmpdir, f.Name()) + break + } + } + assert.NotEmpty(t, updateFile, "update file not found") + + // Check update file + content, err := ioutil.ReadFile(updateFile) + assert.Nil(t, err) + assert.Equal(t, updateContent, content) + + // Wait for update to complete + time.Sleep(500 * time.Millisecond) + + // Update file should be removed when update is completed + assert.NoFileExists(t, updateFile) + + // Get final status + w = srv.testRequest(t, "GET", "/api/status", nil, "") + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) + //nolint:lll + assert.Equal(t, + `{"installing":false,"percent":100,"message":"Installing done.","last_error":"","log":"0% Installing\n0% Determining slot states\n20% Determining slot states done.\n20% Checking bundle\n20% Verifying signature\n40% Verifying signature done.\n40% Checking bundle done.\n40% Checking manifest contents\n60% Checking manifest contents done.\n60% Determining target install group\n80% Determining target install group done.\n80% Updating slots\n80% Checking slot rootfs.0\n90% Checking slot rootfs.0 done.\n90% Copying image to rootfs.0\n100% Copying image to rootfs.0 done.\n100% Updating slots done.\n100% Installing done.\nInstalling `+"`/app/tsgrain-update-raspberrypi3.raucb`"+` succeeded\n"}`, + w.Body.String(), + ) +} + +func TestStatus(t *testing.T) { + srv := newTestServer() + w := srv.testRequest(t, "GET", "/api/status", nil, "") + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, + `{"installing":false,"percent":0,"message":"","last_error":"","log":""}`, + w.Body.String(), + ) +} + +func TestInfo(t *testing.T) { + srv := newTestServer() + w := srv.testRequest(t, "GET", "/api/info", nil, "") + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) + + var info model.SystemInfo + err := json.Unmarshal(w.Body.Bytes(), &info) + assert.Nil(t, err) + + assert.Equal(t, "TSGRain", info.RaucCompatible) + assert.Equal(t, "dev", info.RaucVariant) + assert.Len(t, info.RaucRootfs, 2) +} + +func TestReboot(t *testing.T) { + srv := newTestServer() + testfile := "/tmp/sebrauc_reboot_test" + _ = os.Remove(testfile) + + w := srv.testRequest(t, "POST", "/api/reboot", nil, "") + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, + `{"success":true,"msg":"System is rebooting"}`, + w.Body.String(), + ) + + time.Sleep(5100 * time.Millisecond) + + assert.FileExists(t, testfile) +} diff --git a/src/util/util.go b/src/util/util.go index 2d94270..8a169f0 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -1,6 +1,7 @@ package util import ( + "fmt" "os" "os/exec" "path/filepath" @@ -25,10 +26,24 @@ func CreateDirIfNotExists(dirpath string) error { return nil } -func GetTmpdir(root string) (string, error) { - tmpdir := filepath.Join(root, tmpdirName) +func GetTmpdir(tmpdirPath string) string { + tmpdir := tmpdirPath + // Default temporary directory + if tmpdirPath == "" { + tmpdir = filepath.Join(os.TempDir(), tmpdirName) + } + err := CreateDirIfNotExists(tmpdir) - return tmpdir, err + if err != nil { + panic(fmt.Sprintf("could not create tmpdir %s: %s", tmpdir, err)) + } + + return tmpdir +} + +func RemoveTmpdir(tmpdirPath string) { + tmpdir := GetTmpdir(tmpdirPath) + _ = os.RemoveAll(tmpdir) } func CommandFromString(cmdString string) *exec.Cmd { diff --git a/src/util/util_test.go b/src/util/util_test.go index cd056bb..13dbd9a 100644 --- a/src/util/util_test.go +++ b/src/util/util_test.go @@ -42,27 +42,46 @@ func TestDoesFileExist(t *testing.T) { } func TestTmpdir(t *testing.T) { - td, err := GetTmpdir("/tmp") - if err != nil { - panic(err) + tests := []struct { + name string + path string + }{ + { + name: "default", + path: "", + }, + { + name: "custom", + path: filepath.Join(os.TempDir(), "customTmpdir"), + }, } - tfile := filepath.Join(td, "test.txt") - f, err := os.Create(tfile) - if err != nil { - panic(err) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + td := GetTmpdir(tt.path) + assert.DirExists(t, td) - _, err = f.WriteString("Hello") - if err != nil { - panic(err) - } - err = f.Close() - if err != nil { - panic(err) - } + tfile := filepath.Join(td, "test.txt") + f, err := os.Create(tfile) + if err != nil { + panic(err) + } - assert.FileExists(t, tfile) + _, err = f.WriteString("Hello") + if err != nil { + panic(err) + } + err = f.Close() + if err != nil { + panic(err) + } + + assert.FileExists(t, tfile) + + RemoveTmpdir(tt.path) + assert.NoDirExists(t, td) + }) + } } func TestCommandFromString(t *testing.T) { diff --git a/ui/ui_test.go b/ui/ui_test.go index be2a9a3..6089bbd 100644 --- a/ui/ui_test.go +++ b/ui/ui_test.go @@ -3,21 +3,85 @@ package ui import ( "net/http" "net/http/httptest" + "os" + "path" + "regexp" "testing" + "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" ) func TestUI(t *testing.T) { + tests := []struct { + name string + path string + contains string + cached bool + }{ + { + name: "index_html", + path: "/", + contains: "SEBRAUC", + cached: false, + }, + { + name: "index_html2", + path: "/index.html", + contains: "SEBRAUC", + cached: false, + }, + { + name: "index_js", + path: path.Join("/assets", getIndexJS()), + contains: "SEBRAUC", + cached: true, + }, + } + router := gin.New() Register(router) - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/", nil) - router.ServeHTTP(w, req) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", tt.path, nil) + router.ServeHTTP(w, req) - assert.Equal(t, http.StatusOK, w.Code) - assert.Contains(t, w.Body.String(), "SEBRAUC") - assert.Empty(t, w.Header().Get("Cache-Control")) + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), tt.contains) + + ccHeader := w.Header().Get("Cache-Control") + + if tt.cached { + assert.Equal(t, "public, max-age=604800, immutable", ccHeader) + } else { + assert.Equal(t, "", ccHeader) + } + }) + } +} + +func getIndexJS() string { + baseDir := "ui/dist/assets" + indexExp := regexp.MustCompile(`index\.[0-9a-f]{8}\.js`) + + fixtures.CdProjectRoot() + distDir, err := os.Open(baseDir) + if err != nil { + panic(err) + } + + list, err := distDir.Readdir(-1) + if err != nil { + panic(err) + } + + for _, f := range list { + if indexExp.MatchString(f.Name()) { + return f.Name() + } + } + panic("no index.js found") } From df98c408537fba4b9b1ba8dda7278272b55a789c Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 25 Dec 2021 18:19:41 +0100 Subject: [PATCH 21/27] increase update test delay --- src/server/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/server_test.go b/src/server/server_test.go index 7bb8c4a..f1519e3 100644 --- a/src/server/server_test.go +++ b/src/server/server_test.go @@ -105,7 +105,7 @@ func TestUpdate(t *testing.T) { assert.Equal(t, updateContent, content) // Wait for update to complete - time.Sleep(500 * time.Millisecond) + time.Sleep(1000 * time.Millisecond) // Update file should be removed when update is completed assert.NoFileExists(t, updateFile) From 69318d96d2d789b2c41c4ede6af35665dfcdd335 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sat, 25 Dec 2021 20:52:04 +0100 Subject: [PATCH 22/27] add HTTP basic auth --- .gitignore | 3 +- go.mod | 1 + go.sum | 5 ++ sebrauc.example.toml | 4 ++ src/config/config.go | 34 ++++----- src/config/config_test.go | 11 +++ src/fixtures/testfiles/htpasswd | 3 + src/fixtures/testfiles/sebrauc.toml | 4 ++ src/server/middleware/authentication.go | 36 ++++++++++ src/server/middleware/authentication_test.go | 54 +++++++++++++++ src/server/server.go | 4 ++ src/server/server_test.go | 39 ++++++++++- src/util/errors.go | 1 + src/util/util.go | 33 +++++++++ src/util/util_test.go | 72 ++++++++++++++++++++ 15 files changed, 285 insertions(+), 19 deletions(-) create mode 100644 src/fixtures/testfiles/htpasswd create mode 100644 src/server/middleware/authentication.go create mode 100644 src/server/middleware/authentication_test.go diff --git a/.gitignore b/.gitignore index 1c26218..56dfd25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build tmp -sebrauc.toml +/sebrauc.toml +/htpasswd diff --git a/go.mod b/go.mod index 6e59bf6..92ad7c6 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/stretchr/testify v1.7.0 + github.com/tg123/go-htpasswd v1.2.0 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect diff --git a/go.sum b/go.sum index 376c179..c082427 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ code.thetadev.de/TSGRain/ginzip v0.1.1 h1:+X0L6qumEZiKYSLmM+Q0LqKVHsKvdcg4CVzsEp code.thetadev.de/TSGRain/ginzip v0.1.1/go.mod h1:BH7VkvpP83vPRyMQ8rLIjKycQwGzF+/mFV0BKzg+BuA= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw= +github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -77,10 +79,13 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tg123/go-htpasswd v1.2.0 h1:UKp34m9H467/xklxUxU15wKRru7fwXoTojtxg25ITF0= +github.com/tg123/go-htpasswd v1.2.0/go.mod h1:h7IzlfpvIWnVJhNZ0nQ9HaFxHb7pn5uFJYLlEUJa2sM= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= diff --git a/sebrauc.example.toml b/sebrauc.example.toml index 0d4b5d2..b558ebd 100644 --- a/sebrauc.example.toml +++ b/sebrauc.example.toml @@ -21,6 +21,10 @@ Timeout = 15 Gzip = "default" Brotli = "default" +[Authentication] +Enable = true +PasswdFile = "htpasswd" + # Where to obtain system info from [Sysinfo] ReleaseFile = "/etc/os-release" diff --git a/src/config/config.go b/src/config/config.go index d7a71f8..a46106d 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -9,6 +9,11 @@ import ( "github.com/jinzhu/configor" ) +var ( + cfgFilePaths = []string{"sebrauc", "/etc/sebrauc/sebrauc"} + cfgFileTypes = []string{"toml", "yaml", "yml", "json"} +) + // SEBRAUC config object type Config struct { // Temporary directory @@ -35,6 +40,11 @@ type Config struct { } } + Authentication struct { + Enable bool `default:"false"` + PasswdFile string + } + // Where to obtain system info from Sysinfo struct { ReleaseFile string `default:"/etc/os-release"` @@ -58,25 +68,15 @@ type Config struct { } func findConfigFile(pathIn string) string { - if pathIn != "" { - if !util.DoesFileExist(pathIn) { - panic("specified cfg file does not exist") + fpath, err := util.FindFile(pathIn, cfgFilePaths, cfgFileTypes) + if err != nil { + if pathIn != "" { + panic("cfg file not found: " + err.Error()) } - return pathIn + return "" } - filePaths := []string{"sebrauc", "/etc/sebrauc"} - fileTypes := []string{"toml", "yaml", "yml", "json"} - - for _, f := range filePaths { - for _, t := range fileTypes { - fpath := f + "." + t - if util.DoesFileExist(fpath) { - return fpath - } - } - } - return "" + return fpath } func stripTrailingSlashes(pathIn string) string { @@ -90,7 +90,7 @@ func loadConfig(cfgFile string) *Config { ENVPrefix: "SEBRAUC", }) - err := cfgor.Load(cfg, findConfigFile(cfgFile)) + err := cfgor.Load(cfg, cfgFile) if err != nil { panic(err) } diff --git a/src/config/config_test.go b/src/config/config_test.go index 434da20..332437a 100644 --- a/src/config/config_test.go +++ b/src/config/config_test.go @@ -28,6 +28,9 @@ func TestDefault(t *testing.T) { assert.Equal(t, "", cfg.Tmpdir) + assert.Equal(t, false, cfg.Authentication.Enable) + assert.Equal(t, "", cfg.Authentication.PasswdFile) + assert.Equal(t, "/etc/os-release", cfg.Sysinfo.ReleaseFile) assert.Equal(t, "NAME", cfg.Sysinfo.NameKey) assert.Equal(t, "VERSION", cfg.Sysinfo.VersionKey) @@ -57,6 +60,9 @@ func TestConfigFile(t *testing.T) { assert.Equal(t, "/var/tmp", cfg.Tmpdir) + assert.Equal(t, true, cfg.Authentication.Enable) + assert.Equal(t, "/etc/htpasswd", cfg.Authentication.PasswdFile) + assert.Equal(t, "/etc/release", cfg.Sysinfo.ReleaseFile) assert.Equal(t, "PRETTY_NAME", cfg.Sysinfo.NameKey) assert.Equal(t, "VER", cfg.Sysinfo.VersionKey) @@ -82,6 +88,8 @@ func TestEnvvar(t *testing.T) { os.Setenv("SEBRAUC_SERVER_WEBSOCKET_TIMEOUT", "10") os.Setenv("SEBRAUC_SERVER_COMPRESSION_GZIP", "max") os.Setenv("SEBRAUC_SERVER_COMPRESSION_BROTLI", "false") + os.Setenv("SEBRAUC_AUTHENTICATION_ENABLE", "true") + os.Setenv("SEBRAUC_AUTHENTICATION_PASSWDFILE", "/etc/htpasswd") os.Setenv("SEBRAUC_SYSINFO_RELEASEFILE", "/etc/release") os.Setenv("SEBRAUC_SYSINFO_NAMEKEY", "PRETTY_NAME") os.Setenv("SEBRAUC_SYSINFO_VERSIONKEY", "VER") @@ -104,6 +112,9 @@ func TestEnvvar(t *testing.T) { assert.Equal(t, "/var/tmp", cfg.Tmpdir) + assert.Equal(t, true, cfg.Authentication.Enable) + assert.Equal(t, "/etc/htpasswd", cfg.Authentication.PasswdFile) + assert.Equal(t, "/etc/release", cfg.Sysinfo.ReleaseFile) assert.Equal(t, "PRETTY_NAME", cfg.Sysinfo.NameKey) assert.Equal(t, "VER", cfg.Sysinfo.VersionKey) diff --git a/src/fixtures/testfiles/htpasswd b/src/fixtures/testfiles/htpasswd new file mode 100644 index 0000000..c6382c7 --- /dev/null +++ b/src/fixtures/testfiles/htpasswd @@ -0,0 +1,3 @@ +plain:1234 +md5:$apr1$V2wxHBfb$gBU2yIYjTIeciKapglql6/ +bcrypt:$2y$05$f9rV6uTQEEnNR1saPksExOR31LauUZzpLDhpCrodAvxX3zZ6nLy12 diff --git a/src/fixtures/testfiles/sebrauc.toml b/src/fixtures/testfiles/sebrauc.toml index c806f96..2700a3f 100644 --- a/src/fixtures/testfiles/sebrauc.toml +++ b/src/fixtures/testfiles/sebrauc.toml @@ -15,6 +15,10 @@ Timeout = 10 Gzip = "max" Brotli = "false" +[Authentication] +Enable = true +PasswdFile = "/etc/htpasswd" + [Sysinfo] ReleaseFile = "/etc/release" NameKey = "PRETTY_NAME" diff --git a/src/server/middleware/authentication.go b/src/server/middleware/authentication.go new file mode 100644 index 0000000..c2f33f2 --- /dev/null +++ b/src/server/middleware/authentication.go @@ -0,0 +1,36 @@ +package middleware + +import ( + "net/http" + + "code.thetadev.de/TSGRain/SEBRAUC/src/util" + "github.com/gin-gonic/gin" + "github.com/tg123/go-htpasswd" +) + +var pwdFilePaths = []string{"htpasswd", "/etc/sebrauc/htpasswd"} + +// Authentication requires HTTP basic auth or an active session +func Authentication(pwdFile string) gin.HandlerFunc { + fpath, err := util.FindFile(pwdFile, pwdFilePaths, nil) + if err != nil { + panic("passwd file not found: " + err.Error()) + } + + myauth, err := htpasswd.New(fpath, htpasswd.DefaultSystems, nil) + if err != nil { + panic(err) + } + + return func(c *gin.Context) { + if user, pass, ok := c.Request.BasicAuth(); ok { + if myauth.Match(user, pass) { + c.Set(gin.AuthUserKey, user) + return + } + } + + c.Header("WWW-Authenticate", "Basic realm=\"Authorization Required\"") + c.AbortWithStatus(http.StatusUnauthorized) + } +} diff --git a/src/server/middleware/authentication_test.go b/src/server/middleware/authentication_test.go new file mode 100644 index 0000000..f430032 --- /dev/null +++ b/src/server/middleware/authentication_test.go @@ -0,0 +1,54 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "path/filepath" + "testing" + + "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestAuthentication(t *testing.T) { + testfiles := fixtures.GetTestfilesDir() + pwdfile := filepath.Join(testfiles, "htpasswd") + + router := gin.New() + router.Use(Authentication(pwdfile)) + router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "HelloWorld") }) + + tests := []struct { + name string + }{ + {name: "plain"}, + {name: "md5"}, + {name: "bcrypt"}, + } + + for _, tt := range tests { + t.Run(tt.name+"_ok", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + req.SetBasicAuth(tt.name, "1234") + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "HelloWorld", w.Body.String()) + assert.Empty(t, w.Header().Get("WWW-Authenticate")) + }) + } + + t.Run("fail", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + req.SetBasicAuth("plain", "asdf") + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + assert.Empty(t, w.Body.String()) + assert.Equal(t, "Basic realm=\"Authorization Required\"", + w.Header().Get("WWW-Authenticate")) + }) +} diff --git a/src/server/server.go b/src/server/server.go index 30b4dc3..4f1643b 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -80,6 +80,10 @@ func (srv *SEBRAUCServer) getRouter() *gin.Engine { router.Use(middleware.ErrorHandler(false), middleware.PanicHandler(false)) router.NoRoute(func(c *gin.Context) { c.Error(util.ErrPageNotFound) }) + if srv.config.Authentication.Enable { + router.Use(middleware.Authentication(srv.config.Authentication.PasswdFile)) + } + api := router.Group("/api", middleware.ErrorHandler(true), middleware.PanicHandler(true)) diff --git a/src/server/server_test.go b/src/server/server_test.go index f1519e3..f28dfbb 100644 --- a/src/server/server_test.go +++ b/src/server/server_test.go @@ -28,7 +28,11 @@ type testServer struct { } func newTestServer() *testServer { - sebraucServer := NewServer(config.GetDefault()) + return newTestServerCfg(config.GetDefault()) +} + +func newTestServerCfg(cfg *config.Config) *testServer { + sebraucServer := NewServer(cfg) router := sebraucServer.getRouter() return &testServer{ @@ -168,3 +172,36 @@ func TestReboot(t *testing.T) { assert.FileExists(t, testfile) } + +func TestAuth(t *testing.T) { + testfiles := fixtures.GetTestfilesDir() + + cfg := config.GetDefault() + cfg.Authentication.Enable = true + cfg.Authentication.PasswdFile = filepath.Join(testfiles, "htpasswd") + + srv := newTestServerCfg(cfg) + + t.Run("fail", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + req.SetBasicAuth("plain", "asdf") + srv.router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + assert.Empty(t, w.Body.String()) + assert.Equal(t, "Basic realm=\"Authorization Required\"", + w.Header().Get("WWW-Authenticate")) + }) + + t.Run("ok", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + req.SetBasicAuth("plain", "1234") + srv.router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.NotEmpty(t, w.Body.String()) + assert.Empty(t, w.Header().Get("WWW-Authenticate")) + }) +} diff --git a/src/util/errors.go b/src/util/errors.go index 62427f8..a229d66 100644 --- a/src/util/errors.go +++ b/src/util/errors.go @@ -9,4 +9,5 @@ var ( ErrAlreadyRunning = HttpErrNew("rauc already running", http.StatusConflict) ErrFileDoesNotExist = errors.New("file does not exist") ErrPageNotFound = HttpErrNew("page not found", http.StatusNotFound) + ErrUnauthorized = HttpErrNew("unauthorized", http.StatusUnauthorized) ) diff --git a/src/util/util.go b/src/util/util.go index 8a169f0..e826e45 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -61,3 +61,36 @@ func Reboot(rebootCmd string, t time.Duration) { cmd := CommandFromString(rebootCmd) _ = cmd.Run() } + +func FindFile(explicitPath string, locations, endings []string) (string, error) { + if explicitPath != "" { + if !DoesFileExist(explicitPath) { + return "", fmt.Errorf("file %s not found", explicitPath) + } + return explicitPath, nil + } + + notFound := []string{} + + for _, f := range locations { + if endings != nil { + for _, t := range endings { + fpath := f + "." + t + + if DoesFileExist(fpath) { + return fpath, nil + } else { + notFound = append(notFound, fpath) + } + } + } else { + if DoesFileExist(f) { + return f, nil + } else { + notFound = append(notFound, f) + } + } + } + return "", fmt.Errorf("none of the following files found: %s", + strings.Join(notFound, "; ")) +} diff --git a/src/util/util_test.go b/src/util/util_test.go index 13dbd9a..0ef6a32 100644 --- a/src/util/util_test.go +++ b/src/util/util_test.go @@ -1,6 +1,7 @@ package util import ( + "errors" "os" "path/filepath" "testing" @@ -127,3 +128,74 @@ func TestReboot(t *testing.T) { assert.FileExists(t, testfile) } + +func TestFindFile(t *testing.T) { + testfiles := fixtures.GetTestfilesDir() + + //nolint:lll + tests := []struct { + name string + explicitPath string + locations []string + endings []string + expect string + expectErr error + }{ + { + name: "locations", + explicitPath: "", + locations: []string{filepath.Join(testfiles, "sebrauc")}, + endings: []string{"yml", "toml"}, + expect: filepath.Join(testfiles, "sebrauc.toml"), + expectErr: nil, + }, + { + name: "locations_nf", + explicitPath: "", + locations: []string{filepath.Join(testfiles, "banana")}, + endings: []string{"yml", "toml"}, + expect: "", + expectErr: errors.New("none of the following files found: src/fixtures/testfiles/banana.yml; src/fixtures/testfiles/banana.toml"), + }, + { + name: "no_endings", + explicitPath: "", + locations: []string{filepath.Join(testfiles, "banana"), filepath.Join(testfiles, "os-release")}, + endings: nil, + expect: filepath.Join(testfiles, "os-release"), + expectErr: nil, + }, + { + name: "no_endings_nf", + explicitPath: "", + locations: []string{filepath.Join(testfiles, "banana"), filepath.Join(testfiles, "apple")}, + endings: nil, + expect: "", + expectErr: errors.New("none of the following files found: src/fixtures/testfiles/banana; src/fixtures/testfiles/apple"), + }, + { + name: "explicit", + explicitPath: filepath.Join(testfiles, "sebrauc.toml"), + locations: []string{filepath.Join(testfiles, "banana")}, + endings: []string{"yml", "toml"}, + expect: filepath.Join(testfiles, "sebrauc.toml"), + expectErr: nil, + }, + { + name: "explicit_nf", + explicitPath: filepath.Join(testfiles, "banana.toml"), + locations: []string{filepath.Join(testfiles, "banana")}, + endings: []string{"yml", "toml"}, + expect: "", + expectErr: errors.New("file src/fixtures/testfiles/banana.toml not found"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fpath, err := FindFile(tt.explicitPath, tt.locations, tt.endings) + assert.Equal(t, tt.expectErr, err) + assert.Equal(t, tt.expect, fpath) + }) + } +} From e2c3c2ce6b75670b864c1d19a41c4e757fb60e70 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Mon, 21 Feb 2022 20:14:31 +0100 Subject: [PATCH 23/27] Add README stream: send last broadcast on connect --- Makefile | 6 +- README.md | 1 - README.rst | 460 +++++++++++++++++++++++++++++++ _screenshots/sysinfo.png | Bin 0 -> 159080 bytes _screenshots/update0.png | Bin 0 -> 103870 bytes _screenshots/update1.png | Bin 0 -> 130463 bytes _screenshots/update2.png | Bin 0 -> 128832 bytes _screenshots/update3.png | Bin 0 -> 121450 bytes sebrauc.example.toml | 2 +- src/server/stream/stream.go | 19 +- src/server/stream/stream_test.go | 22 ++ ui/.env.development | 1 - 12 files changed, 499 insertions(+), 12 deletions(-) delete mode 100644 README.md create mode 100644 README.rst create mode 100644 _screenshots/sysinfo.png create mode 100644 _screenshots/update0.png create mode 100644 _screenshots/update1.png create mode 100644 _screenshots/update2.png create mode 100644 _screenshots/update3.png diff --git a/Makefile b/Makefile index 2a1cbee..8da4406 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ UI_DIR=./ui APIDOC_FILE=./src/server/swagger/swagger.yaml -VERSION=$(shell git tag --sort=-version:refname | head -n 1) +VER=$(or ${VERSION},$(shell git tag --sort=-version:refname | head -n 1)) setup: cd ${UI_DIR} && pnpm install @@ -16,10 +16,10 @@ lint: cd ${UI_DIR} && npm run format && npm run lint build-ui: - cd ${UI_DIR} && VITE_VERSION=${VERSION} pnpm run build + cd ${UI_DIR} && pnpm run build build-server: - go build -tags prod -ldflags "-s -w -X code.thetadev.de/TSGRain/SEBRAUC/src/util.version=${VERSION}" -o build/sebrauc ./src/. + go build -tags prod -ldflags "-s -w -X code.thetadev.de/TSGRain/SEBRAUC/src/util.version=${VER}" -o build/sebrauc ./src/. build: build-ui build-server diff --git a/README.md b/README.md deleted file mode 100644 index c074425..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -![SEBRAUC](ui/src/assets/logo_border.svg) diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..ecb1dfc --- /dev/null +++ b/README.rst @@ -0,0 +1,460 @@ +.. image:: ui/src/assets/logo_border.svg + +SEBRAUC ist eine einfach zu bedienende Weboberfläche für den +`RAUC `__ Firmware-Updater, die es +erlaubt, Softwareupdates auf Embedded-Systemen wie dem Raspberry Pi zu +installieren. + +Ich habe die Anwendung für die TSGRain-Bewässerungssteuerung entwickelt, +da es noch keine einfache Möglichkeit gab, RAUC-Updates über eine +Weboberfläche zu installieren. + +Der Updatevorgang funktioniert ähnlich wie bei älteren +WLAN-Routern. Das ``*.raucb``-Firmwarepaket kann über den Browser an das +Gerät übertragen und installiert werden. Im Gegensatz zu OTA-basierten +Lösungen wie `hawkBit `__ +wird keine Internetverbindung, sondern nur ein lokaler WLAN-Access-Point +für die Datenübertragung benötigt. Das macht SEBRAUC ideal für fest +installierte Geräte an abgelegenen Orten wie unser +TSGRain-Bewässerungssystem. + +Funktion +######## + +Startseite +========== + +.. image:: _screenshots/update0.png + :width: 500 + +Auf der Startseite findet der Nutzer einen großen Upload-Button, mit dem +er ein ``*.raucb``-Image auswählen und hochladen kann. + +Update hochladen +================ + +.. image:: _screenshots/update1.png + :width: 500 + +Wurde eine Update-Datei ausgewählt, wird sie auf den Server +hochgeladen und zunächst temporär im Ordner ``/tmp/sebrauc`` gespeichert. +Dabei wird der Uploadfortschritt mit einer kreisförmigen Skala +angezeigt. + +Update installieren +=================== + +.. image:: _screenshots/update2.png + :width: 500 + +Ist der Uplodad der Datei abgeschlossen, beginnt die Installation. +SEBRAUC startet den RAUC-Updater mit dem entsprechenden +Kommandozeilenbefehl. + +Während des Updatevorgangs liefert RAUC folgende Konsolenausgabe: + +.. code-block:: + + installing + 0% Installing + 0% Determining slot states + 20% Determining slot states done. + 20% Checking bundle + 20% Verifying signature + 40% Verifying signature done. + 40% Checking bundle done. + 40% Checking manifest contents + 60% Checking manifest contents done. + 60% Determining target install group + 80% Determining target install group done. + 80% Updating slots + 80% Checking slot rootfs.0 + 90% Checking slot rootfs.0 done. + 90% Copying image to rootfs.0 + 100% Copying image to rootfs.0 done. + 100% Updating slots done. + 100% Installing done. + idle + Installing `/app/tsgrain-update-raspberrypi3.raucb` succeeded + +Aus dieser Konsolenausgabe lässt sich der aktuelle Fortschritt in +Prozent und der Status ermitteln. SEBRAUC leitet diese Daten +in Echtzeit an die Weboberfläche weiter. So kann der Nutzer live +den Updatevorgang mitverfolgen. + +Wenn RAUC eine Fehlermeldung ausgibt, wird diese mit einem roten +Warndreieck am unteren Bildschirmrand angezeigt. + + +Installation abgeschlossen +========================== + +.. image:: _screenshots/update3.png + :width: 500 + +Ist die Installation abgeschlossen, kehrt SEBRAUC zum Startbildschirm +zurück. Am unteren Rand erhält der Nutzer den Hinweis, dass das System +neu gestartet werden muss, um die neue Software in Betrieb zu nehmen. +Drückt man auf den :guilabel:`Restart`-Button, startet das System mit +einer Verzögerung von 3 Sekunden neu. + +Systeminformationen +=================== + +.. image:: _screenshots/sysinfo.png + :width: 500 + +Mit der Schaltfläche oben rechts lässt sich ein Informationsbildschirm +einblenden, der einige Statusinformationen über das System und den +RAUC-Updater anzeigt. +So lässt sich in Erfahrung bringen, welches Betriebssystem in welcher Version +gerade ausgeführt wird und welche der 2 Root-Partitionen gerade aktiv ist. + +Der RAUC-Updater hat eine Konfigurationsoption, mit der festgelegt werden +kann, welche Firmware und Firmware-Variante akzeptiert wird. Diese Werte +werden ebenfalls auf dem Systeminformationsbildschirm angezeigt, +damit der Nutzer sehen kann, welche Firmware-Images mit seinem Gerät +kompatibel sind. + + +Technische Details +################## + +SEBRAUC besteht aus zwei Komponenten: dem in Go geschriebenen Backend +und einer mit Preact und Typescript umgesetzten Single-Page-Weboberfläche. + +Das Backend steuert den RAUC-Updater und stellt die Web-API bereit, die vom +Frontend angesprochen wird. Ich habe für das Backend das +`Gin-Webframework `_ verwendet. Hierbei +handelt es sich um ein minimales und performantes Framework in der Programmiersprache +Go, vergleichbar mit Flask für Python oder Express für +Javascript. Es erlaubt die einfache Erstellung von Routen und Handlern +für Webanfragen, bietet aber im Gegensatz zu größeren Frameworks +wie Django keine eingebaute Datenbankintegration oder Administrationsoberfläche. + +Eine asynchrone Kommunikation vom Server zum Client mittels Websockets +kann mit dem Modul ``github.com/gorilla/websocket`` leicht in eine Gin-Anwendung +integriert werden. + +SEBRAUC verwendet eine solche Websocket-Verbindung, um den aktuellen Zustand +des Systems an das Frontend zu streamen. Dadurch erhält der Nutzer immer die aktuellen +Informationen, ohne dass die Seite neu aufgerufen werden oder die Daten +durch das Frontend in bestimmten Zeitabständen aktualisiert werden müssen. + +Beim Frontend kam das Javascript-Framework `Preact `_ +zum Einsatz, was eine kleinere und schnellere Alternative zu React darstellt. + +Da Preact in komprimierter Form nur 3kB groß ist, eignet es sich perfekt +für kleine Webanwendungen, die schnell laden sollen. So ist die fertige +SEBRAUC-Webanwendung inklusive Styles und Icons nur 22kB groß. Hätte ich hierfür +React oder Vue verwendet, würde die Webseite mindestens doppelt so groß sein. + +Der Autor Ryan Carniato hat in +`diesem Blogpost `_ +verschiedene Webframeworks, darunter React, Vue und Preact miteinander verglichen. +Dafür hat er eine simple Todo-Liste mit allen Frameworks gebaut und +die Größe des generierten Javascript-Codes verglichen. Laut diesem Vergleich ist +die React-Anwendung mit 37.5kB fast siebenmal so groß wie die nur 5.6kB große Preact-Anwendung. + +Um auch das Stylesheet der Anwendung kompakt zu halten, wurde auf fertige +CSS-Komponentenbibliotheken wie Bootstrap verzichtet und stattdessen +selbstgeschriebenes scss verwendet. + + +REST-API +======== + +Die Weboberfläche kommuniziert über REST-API-Anfragen mit dem Server. + +Die API ist im OpenAPI 2.0-Format dokumentiert (die yaml-Dokumentation +befindet sich unter ``src/server/swagger/swagger.yaml``). + +OpenAPI ist ein Standard für maschinenlesbare API-Dokumentationen, +aus denen mit geeigneten Tools menschenlesbare Dokumentationsseiten +sowie fertige API-Clients in verschiedenen Programmiersprachen +erzeugen lassen. + +In diesem Projekt verwende ich ein Tool (``go-swagger``), +das Kommentare und Structs aus dem Programmcode einlesen und daraus die +OpenAPI-Dokumentation generieren kann. Daraus wird dann der Code für den +Typescript-Client für die Weboberfläche generiert. + + +Konfiguration +############# + +Die Konfiguration erfolgt über eine Datei im ``*.json``, ``*.yaml`` oder +``*.toml``-Format. Eine Beispielkonfiguration findet sich +`hier `__. + +Alternativ kann die Konfiguration auch mittels Umgebungsvariablen angepasst +werden, wobei verschachtelte Datenstrukturen mit Unterstrichen +addressiert werden. Beispiel: + +:: + + SERVER_ADDRESS=80 SERVER_COMPRESSION_BROTLI=false ./sebrauc + +SEBRAUC sucht zuerst nach einer Konfigurationsdatei mit dem Namen +``sebrauc.{json,yml,yaml,toml}`` im aktuellen Arbeitsverzeichnis und +anschließend im Ordner ``/etc/sebrauc``. Der Pfad zur +Konfigurationsdatei kann auch manuell mit dem Flag ``-c`` angegeben +werden. + +.. list-table:: + :widths: 35 50 35 + :header-rows: 1 + + * - Option + - Beschreibung + - Standard + * - ``Tmpdir`` + - Temporäres Verzeichnis + - ``"/tmp/sebrauc"`` + * - ``Server.Address`` + - IP-Adresse, von der der Server Verbindungen + + akzeptiert. Leer lassen, um Verbindungen von + + allen Adressen zu akzeptieren. + - ``""`` + * - ``Server.Port`` + - Server-Port + - ``80`` + * - ``Server.Websocket.Ping`` + - Zeitabstand, in denen ein Ping vom + + Websocket-Server an den Client erfolgt. + - ``45`` + * - ``Server.Websocket.Timeout`` + - Timeout in Sekunden, nachdem der Server + + eine Websocket-Verbindung ohne Antwort trennt. + - ``45`` + * - ``Server.Compression.Gzip`` + - Webseiteninhalte mit Gzip komprimieren, + + wenn vom Browser unterstützt. + + Optionen: ``"false"``, ``"default"``, + + ``"min"``, ``"max"``, ``"1"``-``"9"`` + - ``"default"`` + * - ``Server.Compression.Brotli`` + - Webseiteninhalte mit Brotli komprimieren, + + wenn vom Browser unterstützt. + + Optionen: ``"false"``, ``"default"``, + + ``"min"``, ``"max"``, ``"1"``-``"11"`` + - ``"default"`` + * - ``Authentication.Enable`` + - HTTP Basic-Authentifizierung + + (Passwortabfrage im Browser) aktivieren. + - ``false`` + * - ``Authentication.PasswdFile`` + - Apache-kompatible Passwortdatei. + + Suche nach einer Passwortdatei + + unter `./htpasswd` oder `/etc/sebrauc/htpasswd`, + + wenn leergelassen. + - ``""`` + * - ``Sysinfo.ReleaseFile`` + - Datei, aus der OS-Informationen gelesen werden. + - ``"/etc/os-release"`` + * - ``Sysinfo.NameKey`` + - Schlüssel für den Namen des Betriebssystems + + aus der ReleaseFile. + - ``"NAME"`` + * - ``Sysinfo.VersionKey`` + - Schlüssel für die Version des Betriebssystems + + aus der ReleaseFile. + - ``"VERSION"`` + * - ``Sysinfo.HostnameFile`` + - Datei, aus der der Hostname gelesen wird. + - ``"/etc/hostname"`` + * - ``Sysinfo.UptimeFile`` + - Datei, aus der die Betriebszeit des Systems + + gelesen wird. + - ``"/proc/uptime"`` + * - ``Commands.RaucStatus`` + - RAUC-Befehl, um den Status im JSON-Format + + auszulesen. + - ``"rauc status`` + + ``--output-format=json"`` + * - ``Commands.RaucInstall`` + - RAUC-Befehl, um ein Update-Paket zu installieren. + - ``"rauc install"`` + * - ``Commands.Reboot`` + - Befehl, um das System neu zu starten. + - ``"shutdown -r 0"`` + + +Passwortdatei +============= + +Ist die Authentifizierung aktiviert, liest SEBRAUC Nutzernamen und +Passwörter aus Apache-kompatiblen Passwortdateien (standardmäßig unter +``./htpasswd`` oder ``/etc/sebrauc/htpasswd``). + +Die Passwörter können im Klartext oder als Hashes vorliegen. +Gehashte Passwörter lassen sich mit mit dem Befehl ``htpasswd -nB username`` +erzeugen. Hierfür wird unter Linux das Paket ``apache-tools`` benötigt. + +.. code-block:: + + # Klartextpasswort (nur zum Testen verwenden) + username:password + + # Gehashtes Passwort + test:$2y$05$EzzPOZprUhPE.1ru1gM8du0ZNpmsU40EFDZ1PmzZtBzkMHsJVK1au + + +Entwicklung und Build +##################### + +Systemvoraussetzungen +===================== + +Um SEBRAUC zu kompilieren werden folgende Programme benötigt: + +- Golang (Version 1.16 oder neuer) +- NodeJS +- PNPM-Paketmanager (installieren mit ``npm i -g pnpm``) + +Zur Entwicklung werden zusätzlich folgende Tools verwendet: + +- `golangci-lint `_ (Überprüft Go-Code auf + Formatierung, Code Style und bestimmete Fehler) +- `air `_ (Kompiliert und startet + Go-Anwendungen bei Codeänderungen neu) +- `go-swagger `_ + (Generierung einer OpenAPI-Dokumentation aus Go-Servercode, der nach + einem bestimmten Schema kommentiert wurde) +- `openapi-generator `_ + (Erzeugt fertige API-Clients aus OpenAPI-Dokumentationen) + +Befehle +======= + +Das Projekt enthät eine Makefile, mit der sich oft benötigte Befehle +schnell ausführen lassen. + +``make setup`` + Dieser Befehl muss vor dem Arbeiten an dem Projekt ausgeführt werden. + Hiermit werden sämtliche Abhängigkeiten für das Frontend + +``make lint`` + Überprüfe den Backend-Code mit ``golangci-lint`` auf Fehler und schlechte + Programmierpraktiken. + Zudem wird der Typescript-Code im Frontend mit dem Typescript-Compiler + auf Typfehler geprüft. + +``make build`` + Baue die Produktionsversion der Software. + Die Softwareversion, die in die Software einkompiliert wird, + wird aus dem aktuellsten Git-Tag ermittelt. + +``make test`` + Führt die Tests für das Backend aus. + +``make generate-apidoc`` + Liest die API-Dokumentation aus den Kommentaren im Quellcode ein + und produziert daraus eine Swagger-Dokumentationsdatei im yaml-Format. + +``make generate-apiclient`` + Erzeugt den Typescript-API-Client für das Frontend aus der Swagger + API-Dokumentation. Um die API-Dokumentation zu aktualisieren, muss + vorher ``make generate-apidoc`` ausgeführt werden. + + +Um die SEBRAUC-Software zu bauen, müssen nach dem Herunterladen des Repositorys +folgende Befehle ausgeführt werden: + +.. code-block:: sh + + # Abhängigkeiten installieren + make setup + + # Build für die CPU-Architektur des laufenden Systems + make build + + # Build für andere CPU-Architekturen (Cross-Compile) + GOARCH=arm make build + +Die kompilierte Anwendung befindet wird im Ordner ``build`` +abgelegt. + + +RAUC-Mock +========= + +Um SEBRAUC testweise auf einem Entwicklerrechner ohne den RAUC-Updater +laufen zu lassen, habe ich ein kleines Go-Programm geschrieben, das +die Konsolenausgabe von RAUC simuliert. + +Man kann dieses Programm anstelle der echten RAUC-Befehle in die +Konfigurationsdatei eintragen. + +.. code-block:: toml + + # Commands to be run by SEBRAUC + [Commands] + # RAUC status command (outputs updater status in json format) + RaucStatus = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock status --output-format=json" + # RAUC install command (installs FW image passed as argument) + RaucInstall = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock install" + +Wird SEBRAUC im Testmodus (d.h. ohne das Build-Flag ``-tags prod``) gebaut, +führt das Programm stets den Mock-RAUC-Befehl aus und ignoriert die konfigurierten +Befehle. Ob sich SEBRAUC im Testmodus befindet, lässt sich an der Hinweisnachricht +beim Start erkennen. + +.. code-block:: text + + Test mode active - no update operations are executed. + Build with -tags prod to enable live mode. + +Das Verhalten von ``rauc_mock`` kann mittels Umgebungsvariablen angepasst werden. +Ist die Variable ``RAUC_MOCK_FAIL`` gesetzt, wird eine fehlerhafte Installation +simuliert. Mit dem Setzen der Variable ``RAUC_MOCK_TEST`` reduziert man die Verzögerung, +mit der die Nachrichten angezeigt werden, von 500 auf 10 Millisekunden. Diese Option +dient dazu, automatische Tests zu beschleunigen, während die Wartezeit bei manuellen +Tests lang genug ist, um sämtliche Statusnachrichten lesen zu können. + + +Auto-Reload des Backends +======================== + +Wenn man am Backend-Code arbeitet und die Webseite oder API dabei gleichzeitig +testen möchte, hat man mit kompilierten Sprachen wie Go das Problem, dass sich +Codeänderungen nicht sofort auf das Verhalten der Anwendung auswirken. +Erst nach einer Rekompilierung und einem Neustart des Servers ist eine +Änderung wirksam. + +Glücklicherweise gibt es Tools wie ``air``, die die Quellcodedateien überwachen +und die Anwendung bei Änderung automatisch kompilieren und neu starten. + +Um den SEBRAUC-Server mit Auto-Reload zu starten, +muss man einfach den Befehl ``air`` im Stammverzeichnis des Projekts ausführen + + +Frontend +======== + +Um den Entwicklungsserver für das Frontend zu starten, navigiert man in das +``ui``-Verzeichnis und startet den Server mit dem Befehl ``npm run dev``. + +Im Entwicklungsmodus kommuniziert das Frontend mit dem Backend unter der +Adresse ``localhost:8080``. Man kann also parallel SEBRAUC mit entsprechender +Konfiguration (Port: 8080) starten, um das Frontend mit einem funktionsfähigen +Server zu testen. diff --git a/_screenshots/sysinfo.png b/_screenshots/sysinfo.png new file mode 100644 index 0000000000000000000000000000000000000000..a6ca4e2a6648a1d3ca324a91491e0f8e0fe1da26 GIT binary patch literal 159080 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 literal 0 HcmV?d00001 diff --git a/_screenshots/update0.png b/_screenshots/update0.png new file mode 100644 index 0000000000000000000000000000000000000000..faa7ebe3bc320597d59864dd912344f350926abe GIT binary patch literal 103870 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
fNFROtR%7M20AGcI&fK(s(RBHMy=Wn(AuxjgTK< zDG9v!KqLI^s+ILY2W!SQtJ+@W`96%pH^chf2J6<3H+W6IUehk$pk&L?9E~crFw`yI zEa1pFYjyMkOfBNYx(=Q^^Fb^Vvm5zQiSk!2LEv^6ChFv*2!Cyg553|yydcBvh^jA3 zl^(bE+@1Nfcg^ly_+YCbYZ?{n5SyoUxLD6tcP=mUb@J*=x?J>B){AT9_aUchFDWln zg9gW#;GLknk)tr0ROq;f>l;vXgc@V7hhI4Z=4CJFi%M<^LX*gPu)x?Ob8->~sKW?9 zNwcL=t+=4=E(JB!2Dn)5_fJUo)Jc2Sfz=Wa>%K9_-(o#sn%`{f0KBE|87H1u9_B=K z*B!hSUCxh6Ot==Vw^6sWoLH(nDiCn7NGX>0v1~IXE`lT0dMjydb{_61F@C5mNvO81 zS@PC+il*na;o_4pTqDN|HXk%?;H=Ri_pF!N;#N<1@`c~oXbQa;ih-Kvccwu`=IwLT z%ZPqS+K8ro{?0~c5ng22mGZ71M+;wuxZhqVS+$6+)-6-vq#TXg7)d;**68%@`iL8# z3fYeH{c#Vn>!xMB;%>x|=J5$aO1KDvEgP;b^^PPl!%!Ah&q3+#ltxYF5lGYhMoyN7 zrY>nBdzWFQfA}k?hM1F1COkY^&=kUy9_vh>6K(RQ8!5!n<6JWf>FJ0h1utR9Upd_& zS7yPYWNt*?apx|2T10)7sV(Kpi|lFa zWh$WN9^vFHvROPhRB6;;Q9m+D@yTFOAh0miT!cYQaQ}*+9M4}~P1_44gTtsw{7f_w z<_Jl6QNe9vPt!iP*F>)KSIsdojL@9x>RG5++Rojpe?_%WDy)v<)Q%vf5&Br}%sIyJ zjU#y=DLcTiyGv}41IQTFeXo_?I~2-Bc=T(Ux>SpV94;ypYuG?Q=<290gAL%=EoB9a z-tj&=nt%6U$g_RGJysm^+;p=$yhV+abKQ_#MU&YA?#tJddz%ZT6|b)$l;VJbEhAbpZlG9 zW^dCf%or^`O~c76_Xq^UnI_|GGqDDl>t@`YQz;HVGHE;$M659fe+7eaGBis0+-fWH z&v1K{oP`(RLD(N7QLWqv%vCb*PPGMet}y)L{&Bs zNKs5MLxMU&EpUa6xMSjOSg96q_FgE!KtnKX_9b0bY5*wojWEUexq_6D+Y|Z|!hI@q zTZ4tBJD;j9*^Nt7Y~aSemdS*W<_E)HmS|eQvpx6dxe6HP1s$fJ!q15d11q8(XwVqZ0)v%R;(0h zw)jZ7dWpK6_UiJH=x}>BY2TtVybTvHgIGf2wkz{}1)i>W5v;T|qU>;)!LK!JB?O^j z8=1quYGc(%F%S|L*=b(rxE;x^5v-e+lNq_Ul{Bc{S2$!-t)=}fQFC?8*(P+z)@c}; zfngm53sHH?4I|POBB2_t?;a8J&%cGHpbfCA{PQ+;)@BW%Jju=zh}6lRD2{WTV;Pm( z;*XL&Sny4yxuscRq~yi+Ku)ElaFaR_-BpCrWGJpJ-U7k5@HmpCW1OKKK?hjz@B zFZR(FS-&`Wk5_KTI85=#xGgW6rO7RIZEc

nPC{h_jgxxgq@SqYxGC(4!vQP-jV7 zwukcrxAr@h`wt%S7TP*g7(IC^CNn$Jt}$7f5f6@Q>aOwZh>k&u>P>Ipr{v z-VhHMcycpp{#YzT5qK%aOdL-_s2DDFWUÁ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 z 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

*fe$ud=BM_?$~?1(X;CKJXRU# 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 =y@i9Uz52(i+CmKuUM^E%<<}sv0LV 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 PfFB7FS>X~v{dfNdB+bc@ literal 0 HcmV?d00001 diff --git a/_screenshots/update2.png b/_screenshots/update2.png new file mode 100644 index 0000000000000000000000000000000000000000..7dfcfed0dfba5a1f1e0df7ade9ad96d15f09cf1f GIT binary patch literal 128832 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 DXVLJ~ literal 0 HcmV?d00001 diff --git a/_screenshots/update3.png b/_screenshots/update3.png new file mode 100644 index 0000000000000000000000000000000000000000..399edef4f17ac5e2f0ece0de53466c2e0460414d GIT binary patch literal 121450 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
fNFROtR%7M20AGcI&fK(s(RBHMy=Wn(AuxjgTK< zDG9v!KqLI^s+ILY2W!SQtJ+@W`96%pH^chf2J6<3H+W6IUehk$pk&L?9E~crFw`yI zEa1pFYjyMkOfBNYx(=Q^^Fb^Vvm5zQiSk!2LEv^6ChFv*2!Cyg553|yydcBvh^jA3 zl^(bE+@1Nfcg^ly_+YCbYZ?{n5SyoUxLD6tcP=mUb@J*=x?J>B){AT9_aUchFDWln zg9gW#;GLknk)tr0ROq;f>l;vXgc@V7hhI4Z=4CJFi%M<^LX*gPu)x?Ob8->~sKW?9 zNwcL=t+=4=E(JB!2Dn)5_fJUo)Jc2Sfz=Wa>%K9_-(o#sn%`{f0KBE|87H1u9_B=K z*B!hSUCxh6Ot==Vw^6sWoLH(nDiCn7NGX>0v1~IXE`lT0dMjydb{_61F@C5mNvO81 zS@PC+il*na;o_4pTqDN|HXk%?;H=Ri_pF!N;#N<1@`c~oXbQa;ih-Kvccwu`=IwLT z%ZPqS+K8ro{?0~c5ng22mGZ71M+;wuxZhqVS+$6+)-6-vq#TXg7)d;**68%@`iL8# z3fYeH{c#Vn>!xMB;%>x|=J5$aO1KDvEgP;b^^PPl!%!Ah&q3+#ltxYF5lGYhMoyN7 zrY>nBdzWFQfA}k?hM1F1COkY^&=kUy9_vh>6K(RQ8!5!n<6JWf>FJ0h1utR9Upd_& zS7yPYWNt*?apx|2T10)7sV(Kpi|lFa zWh$WN9^vFHvROPhRB6;;Q9m+D@yTFOAh0miT!cYQaQ}*+9M4}~P1_44gTtsw{7f_w z<_Jl6QNe9vPt!iP*F>)KSIsdojL@9x>RG5++Rojpe?_%WDy)v<)Q%vf5&Br}%sIyJ zjU#y=DLcTiyGv}41IQTFeXo_?I~2-Bc=T(Ux>SpV94;ypYuG?Q=<290gAL%=EoB9a z-tj&=nt%6U$g_RGJysm^+;p=$yhV+abKQ_#MU&YA?#tJddz%ZT6|b)$l;VJbEhAbpZlG9 zW^dCf%or^`O~c76_Xq^UnI_|GGqDDl>t@`YQz;HVGHE;$M659fe+7eaGBis0+-fWH z&v1K{oP`(RLD(N7QLWqv%vCb*PPGMet}y)L{&Bs zNKs5MLxMU&EpUa6xMSjOSg96q_FgE!KtnKX_9b0bY5*wojWEUexq_6D+Y|Z|!hI@q zTZ4tBJD;j9*^Nt7Y~aSemdS*W<_E)HmS|eQvpx6dxe6HP1s$fJ!q15d11q8(XwVqZ0)v%R;(0h zw)jZ7dWpK6_UiJH=x}>BY2TtVybTvHgIGf2wkz{}1)i>W5v;T|qU>;)!LK!JB?O^j z8=1quYGc(%F%S|L*=b(rxE;x^5v-e+lNq_Ul{Bc{S2$!-t)=}fQFC?8*(P+z)@c}; zfngm53sHH?4I|POBB2_t?;a8J&%cGHpbfCA{PQ+;)@BW%Jju=zh}6lRD2{WTV;Pm( z;*XL&Sny4yxuscRq~yi+Ku)ElaFaR_-BpCrWGJpJ-U7k5@HmpCW1OKKK?hjz@B zFZR(FS-&`Wk5_KTI85=#xGgW6rO7RIZEc

nPC{h_jgxxgq@SqYxGC(4!vQP-jV7 zwukcrxAr@h`wt%S7TP*g7(IC^CNn$Jt}$7f5f6@Q>aOwZh>k&u>P>Ipr{v z-VhHMcycpp{#YzT5qK%aOdL-_s2DDFWUÁ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 z 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 literal 0 HcmV?d00001 diff --git a/sebrauc.example.toml b/sebrauc.example.toml index b558ebd..5f95ce0 100644 --- a/sebrauc.example.toml +++ b/sebrauc.example.toml @@ -9,7 +9,7 @@ Tmpdir = "/tmp/sebrauc" # IP address to accept connections from Address = "" # Port to listen at -Port = 8001 +Port = 8080 # Websocket connection settings: Ping interval/Timeout in seconds [Server.Websocket] diff --git a/src/server/stream/stream.go b/src/server/stream/stream.go index 1e58ed6..6a91fbc 100644 --- a/src/server/stream/stream.go +++ b/src/server/stream/stream.go @@ -16,12 +16,13 @@ import ( // The API provides a handler for a WebSocket stream API. type API struct { - clients map[uint]*client - lock sync.RWMutex - pingPeriod time.Duration - pongTimeout time.Duration - upgrader *websocket.Upgrader - counter *util.Counter + clients map[uint]*client + lock sync.RWMutex + pingPeriod time.Duration + pongTimeout time.Duration + upgrader *websocket.Upgrader + counter *util.Counter + lastBroadcast []byte } // New creates a new instance of API. @@ -64,6 +65,7 @@ func (a *API) Broadcast(msg []byte) { for _, client := range a.clients { client.write <- msg } + a.lastBroadcast = msg } func (a *API) remove(remove *client) { @@ -76,6 +78,11 @@ func (a *API) register(client *client) { a.lock.Lock() defer a.lock.Unlock() a.clients[client.id] = client + + // Send new clients the last broadcast so they get the current state + if a.lastBroadcast != nil { + client.write <- a.lastBroadcast + } } func (a *API) Handle(ctx *gin.Context) { diff --git a/src/server/stream/stream_test.go b/src/server/stream/stream_test.go index 15de1f5..771e6b2 100644 --- a/src/server/stream/stream_test.go +++ b/src/server/stream/stream_test.go @@ -281,6 +281,28 @@ func TestBroadcast(t *testing.T) { expectMessage(testMsg2, client2, client3) } +func TestLastBroadcast(t *testing.T) { + defer leaktest.Check(t)() + server, api := bootTestServer() + defer server.Close() + + wsURL := wsURL(server.URL) + + testMsg1 := []byte("hello1") + api.Broadcast(testMsg1) + + client1 := testClient(t, wsURL) + defer client1.conn.Close() + + client2 := testClient(t, wsURL) + defer client2.conn.Close() + + // the server may take some time to register the client + time.Sleep(100 * time.Millisecond) + + expectMessage(testMsg1, client1, client2) +} + func Test_sameOrigin_returnsTrue(t *testing.T) { mode.Set(mode.Prod) defer mode.Set(mode.Dev) diff --git a/ui/.env.development b/ui/.env.development index 3eba399..029df76 100644 --- a/ui/.env.development +++ b/ui/.env.development @@ -1,2 +1 @@ -VITE_VERSION=dev VITE_API_HOST=127.0.0.1:8080 From e98b29d6669dab9cf517a3d44d9f012eed738ca3 Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 8 May 2022 02:19:13 +0200 Subject: [PATCH 24/27] add woodpecker ci --- src/rauc/rauc.go | 6 +++++- src/sysinfo/sysinfo.go | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/rauc/rauc.go b/src/rauc/rauc.go index 6254e0b..d4b81dc 100644 --- a/src/rauc/rauc.go +++ b/src/rauc/rauc.go @@ -122,7 +122,11 @@ func (r *Rauc) GetStatus() model.RaucStatus { } func (r *Rauc) GetStatusJson() []byte { - statusJson, _ := json.Marshal(r.status) + statusJson, err := json.Marshal(r.status) + if err != nil { + return []byte{} + } + return statusJson } diff --git a/src/sysinfo/sysinfo.go b/src/sysinfo/sysinfo.go index 99f0642..f2aabc0 100644 --- a/src/sysinfo/sysinfo.go +++ b/src/sysinfo/sysinfo.go @@ -46,8 +46,8 @@ type osRelease struct { var rexpUptime = regexp.MustCompile(`^\d+`) func New(cmdRaucStatus string, releaseFile string, nameKey string, versionKey string, - hostnameFile string, uptimeFile string) *Sysinfo { - + hostnameFile string, uptimeFile string, +) *Sysinfo { return &Sysinfo{ cmdRaucStatus: cmdRaucStatus, releaseFile: releaseFile, @@ -127,7 +127,8 @@ func getFSNameFromBootname(rfslist map[string]model.Rootfs, bootname string) str } func mapSysinfo(rinf raucInfo, osr osRelease, uptime int, - hostname string) model.SystemInfo { + hostname string, +) model.SystemInfo { rfslist := mapRootfs(rinf) return model.SystemInfo{ From 4d7c4646bb3ec419015403481782af03a42c9f8b Mon Sep 17 00:00:00 2001 From: Theta-Dev Date: Sun, 8 May 2022 02:21:13 +0200 Subject: [PATCH 25/27] add woodpecker ci config --- .woodpecker.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .woodpecker.yml diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..69668a7 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,14 @@ +pipeline: + frontend: + image: node:16-alpine + commands: + - cd ui + - npm install -g pnpm + - pnpm install + - pnpm run build + backend: + image: golangci/golangci-lint:latest + commands: + - go get -t ./src/... + - golangci-lint run --timeout 5m + - go test -v ./src/... From 7620f0d9f2b0315e8634a963b993388a6984caa1 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Tue, 3 Jan 2023 21:22:19 +0100 Subject: [PATCH 26/27] ci: add woodpecker.yml --- ui/pnpm-lock.yaml | 4 +++- woodpecker.yml | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 woodpecker.yml diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index 1110f3a..fe7d84e 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: 5.3 +lockfileVersion: 5.4 specifiers: "@mdi/js": ^6.5.95 @@ -282,6 +282,8 @@ packages: } engines: {node: ">=6.0.0"} hasBin: true + dependencies: + "@babel/types": 7.16.0 dev: true /@babel/plugin-syntax-jsx/7.16.0: diff --git a/woodpecker.yml b/woodpecker.yml new file mode 100644 index 0000000..2271c55 --- /dev/null +++ b/woodpecker.yml @@ -0,0 +1,14 @@ +pipeline: + Frontend build: + image: node:16-alpine + commands: + - cd ui + - npm install -g pnpm + - pnpm install + - pnpm run build + Backend test: + image: golangci/golangci-lint:latest + commands: + - go get -t ./src/... + - golangci-lint run --timeout 5m + - go test -v ./src/... From 28e51df5b8112a0464e39509631e817b7acf3b52 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Tue, 3 Jan 2023 21:47:22 +0100 Subject: [PATCH 27/27] fix: lint errors --- src/server/server.go | 97 ++++++++++++++++++++------------------- src/server/server_test.go | 3 +- 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/src/server/server.go b/src/server/server.go index 4f1643b..fe41c45 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -1,6 +1,6 @@ // SEBRAUC // -// REST API for the SEBRAUC firmware updater +// # REST API for the SEBRAUC firmware updater // // --- // Schemes: http, https @@ -124,31 +124,33 @@ func (srv *SEBRAUCServer) Run() error { // swagger:operation POST /update startUpdate // -// Start the update process +// # Start the update process // // --- // consumes: // - multipart/form-data // produces: [application/json] // parameters: -// - name: updateFile -// in: formData -// description: RAUC firmware image file (*.raucb) -// required: true -// type: file +// - name: updateFile +// in: formData +// description: RAUC firmware image file (*.raucb) +// required: true +// type: file +// // responses: -// 200: -// description: Ok -// schema: -// $ref: "#/definitions/StatusMessage" -// 409: -// description: already running -// schema: -// $ref: "#/definitions/Error" -// 500: -// description: Server Error -// schema: -// $ref: "#/definitions/Error" +// +// 200: +// description: Ok +// schema: +// $ref: "#/definitions/StatusMessage" +// 409: +// description: already running +// schema: +// $ref: "#/definitions/Error" +// 500: +// description: Server Error +// schema: +// $ref: "#/definitions/Error" func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) { file, err := c.FormFile("updateFile") if err != nil { @@ -181,38 +183,40 @@ func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) { // swagger:operation GET /status getStatus // -// Get the current status of the RAUC updater +// # Get the current status of the RAUC updater // // --- // produces: [application/json] // responses: -// 200: -// description: Ok -// schema: -// $ref: "#/definitions/RaucStatus" -// 500: -// description: Server Error -// schema: -// $ref: "#/definitions/Error" +// +// 200: +// description: Ok +// schema: +// $ref: "#/definitions/RaucStatus" +// 500: +// description: Server Error +// schema: +// $ref: "#/definitions/Error" func (srv *SEBRAUCServer) controllerStatus(c *gin.Context) { c.JSON(http.StatusOK, srv.updater.GetStatus()) } // swagger:operation GET /info getInfo // -// Get the current system info +// # Get the current system info // // --- // produces: [application/json] // responses: -// 200: -// description: Ok -// schema: -// $ref: "#/definitions/SystemInfo" -// 500: -// description: Server Error -// schema: -// $ref: "#/definitions/Error" +// +// 200: +// description: Ok +// schema: +// $ref: "#/definitions/SystemInfo" +// 500: +// description: Server Error +// schema: +// $ref: "#/definitions/Error" func (srv *SEBRAUCServer) controllerInfo(c *gin.Context) { info, err := srv.sysinfo.GetSysinfo() if err != nil { @@ -224,19 +228,20 @@ func (srv *SEBRAUCServer) controllerInfo(c *gin.Context) { // swagger:operation POST /reboot startReboot // -// Reboot the system +// # Reboot the system // // --- // produces: [application/json] // responses: -// 200: -// description: Ok -// schema: -// $ref: "#/definitions/StatusMessage" -// 500: -// description: Server Error -// schema: -// $ref: "#/definitions/Error" +// +// 200: +// description: Ok +// schema: +// $ref: "#/definitions/StatusMessage" +// 500: +// description: Server Error +// schema: +// $ref: "#/definitions/Error" func (srv *SEBRAUCServer) controllerReboot(c *gin.Context) { go util.Reboot(srv.config.Commands.Reboot, 5*time.Second) diff --git a/src/server/server_test.go b/src/server/server_test.go index f28dfbb..30b25b1 100644 --- a/src/server/server_test.go +++ b/src/server/server_test.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "io" - "io/ioutil" "mime/multipart" "net/http" "net/http/httptest" @@ -104,7 +103,7 @@ func TestUpdate(t *testing.T) { assert.NotEmpty(t, updateFile, "update file not found") // Check update file - content, err := ioutil.ReadFile(updateFile) + content, err := os.ReadFile(updateFile) assert.Nil(t, err) assert.Equal(t, updateContent, content)