diff --git a/.codecov.yml b/.codecov.yml index 97798c7..d4f9742 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2021-06-23T20:37:16Z by kres latest. +# Generated on 2024-01-11T19:00:51Z by kres latest. codecov: require_ci_to_pass: false @@ -9,7 +9,7 @@ coverage: status: project: default: - target: 50% + target: 30% threshold: 0.5% base: auto if_ci_failed: success diff --git a/.dockerignore b/.dockerignore index ca5d543..ed2b746 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2023-03-17T16:37:39Z by kres 6bde0cc. +# Generated on 2024-01-11T19:00:51Z by kres latest. * !kmsg.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..c38a623 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,76 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-01-11T18:39:51Z by kres latest. + +name: default +concurrency: + group: ${{ github.head_ref || github.run_id }} + cancel-in-progress: true +"on": + push: + branches: + - main + - release-* + tags: + - v* + pull_request: + branches: + - main + - release-* +jobs: + default: + permissions: + actions: read + contents: write + issues: read + packages: write + pull-requests: read + runs-on: + - self-hosted + - generic + if: (!startsWith(github.head_ref, 'renovate/') && !startsWith(github.head_ref, 'dependabot/')) + services: + buildkitd: + image: moby/buildkit:v0.12.4 + options: --privileged + ports: + - 1234:1234 + volumes: + - /var/lib/buildkit/${{ github.repository }}:/var/lib/buildkit + - /usr/etc/buildkit/buildkitd.toml:/etc/buildkit/buildkitd.toml + steps: + - name: checkout + uses: actions/checkout@v4 + - name: Unshallow + run: | + git fetch --prune --unshallow + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: remote + endpoint: tcp://localhost:1234 + - name: base + run: | + make base + - name: unit-tests + run: | + make unit-tests + - name: unit-tests-race + run: | + make unit-tests-race + - name: coverage + run: | + make coverage + - name: lint + run: | + make lint + - name: release-notes + if: startsWith(github.ref, 'refs/tags/') + run: | + make release-notes + - name: Release + if: startsWith(github.ref, 'refs/tags/') + uses: crazy-max/ghaction-github-release@v2 + with: + body_path: _out/RELEASE_NOTES.md + draft: "true" diff --git a/.github/workflows/slack-notify.yaml b/.github/workflows/slack-notify.yaml new file mode 100644 index 0000000..f120d80 --- /dev/null +++ b/.github/workflows/slack-notify.yaml @@ -0,0 +1,92 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2024-01-11T18:39:51Z by kres latest. + +name: slack-notify +"on": + workflow_run: + workflows: + - default + types: + - completed +jobs: + slack-notify: + runs-on: + - self-hosted + - generic + if: github.event.workflow_run.conclusion != 'skipped' + steps: + - name: Get PR number + id: get-pr-number + if: github.event.workflow_run.event == 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + run: | + echo pull_request_number=$(gh pr view -R ${{ github.repository }} ${{ github.event.workflow_run.head_repository.owner.login }}:${{ github.event.workflow_run.head_branch }} --json number --jq .number) >> $GITHUB_OUTPUT + - name: Slack Notify + uses: slackapi/slack-github-action@v1 + with: + channel-id: proj-talos-maintainers + payload: | + { + "attachments": [ + { + "color": "${{ github.event.workflow_run.conclusion == 'success' && '#2EB886' || github.event.workflow_run.conclusion == 'failure' && '#A30002' || '#FFCC00' }}", + "fallback": "test", + "blocks": [ + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "${{ github.event.workflow_run.event == 'pull_request' && format('*Pull Request:* {0} (`{1}`)\n<{2}/pull/{3}|{4}>', github.repository, github.ref_name, github.event.repository.html_url, steps.get-pr-number.outputs.pull_request_number, github.event.workflow_run.display_title) || format('*Build:* {0} (`{1}`)\n<{2}/commit/{3}|{4}>', github.repository, github.ref_name, github.event.repository.html_url, github.sha, github.event.workflow_run.display_title) }}" + }, + { + "type": "mrkdwn", + "text": "*Status:*\n`${{ github.event.workflow_run.conclusion }}`" + } + ] + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Author:*\n`${{ github.actor }}`" + }, + { + "type": "mrkdwn", + "text": "*Event:*\n`${{ github.event.workflow_run.event }}`" + } + ] + }, + { + "type": "divider" + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Logs" + }, + "url": "${{ github.event.workflow_run.html_url }}" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Commit" + }, + "url": "${{ github.event.repository.html_url }}/commit/${{ github.sha }}" + } + ] + } + ] + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.golangci.yml b/.golangci.yml index 4b9b60c..f68ba68 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2023-03-02T13:41:04Z by kres latest. +# Generated on 2024-01-11T18:39:51Z by kres latest. # options for analysis running run: @@ -74,9 +74,8 @@ linters-settings: govet: check-shadowing: true enable-all: true - depguard: - list-type: blacklist - include-go-root: false + disable: + - loopclosure lll: line-length: 200 tab-width: 4 @@ -118,6 +117,10 @@ linters-settings: cyclop: # the maximal code complexity to report max-complexity: 20 + # depguard: + # Main: + # deny: + # - github.com/OpenPeeDeeP/depguard # this is just an example linters: enable-all: true @@ -145,6 +148,11 @@ linters: - typecheck - varnamelen - wrapcheck + - depguard # Disabled because starting with golangci-lint 1.53.0 it doesn't allow denylist alone anymore + - tagalign + - inamedparam + - testifylint # complains about our assert recorder and has a number of false positives for assert.Greater(t, thing, 1) + - protogetter # complains about us using Value field on typed spec, instead of GetValue which has a different signature # abandoned linters for which golangci shows the warning that the repo is archived by the owner - interfacer - maligned diff --git a/.kres.yaml b/.kres.yaml new file mode 100644 index 0000000..7aadab8 --- /dev/null +++ b/.kres.yaml @@ -0,0 +1,4 @@ +--- +kind: service.CodeCov +spec: + targetThreshold: 30 diff --git a/Dockerfile b/Dockerfile index d04aa82..cb0c535 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -# syntax = docker/dockerfile-upstream:1.5.2-labs +# syntax = docker/dockerfile-upstream:1.6.0-labs # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2023-03-17T16:37:39Z by kres 6bde0cc. +# Generated on 2024-01-11T18:39:51Z by kres latest. ARG TOOLCHAIN @@ -10,9 +10,9 @@ ARG TOOLCHAIN FROM scratch AS generate # runs markdownlint -FROM docker.io/node:19.7.0-alpine3.16 AS lint-markdown +FROM docker.io/node:21.4.0-alpine3.18 AS lint-markdown WORKDIR /src -RUN npm i -g markdownlint-cli@0.33.0 +RUN npm i -g markdownlint-cli@0.37.0 RUN npm i sentences-per-line@0.2.1 COPY .markdownlint.json . COPY ./README.md ./README.md @@ -27,27 +27,32 @@ FROM --platform=${BUILDPLATFORM} toolchain AS tools ENV GO111MODULE on ARG CGO_ENABLED ENV CGO_ENABLED ${CGO_ENABLED} +ARG GOTOOLCHAIN +ENV GOTOOLCHAIN ${GOTOOLCHAIN} +ARG GOEXPERIMENT +ENV GOEXPERIMENT ${GOEXPERIMENT} ENV GOPATH /go +ARG DEEPCOPY_VERSION +RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install github.com/siderolabs/deep-copy@${DEEPCOPY_VERSION} \ + && mv /go/bin/deep-copy /bin/deep-copy ARG GOLANGCILINT_VERSION RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCILINT_VERSION} \ && mv /go/bin/golangci-lint /bin/golangci-lint -ARG GOFUMPT_VERSION -RUN go install mvdan.cc/gofumpt@${GOFUMPT_VERSION} \ - && mv /go/bin/gofumpt /bin/gofumpt RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install golang.org/x/vuln/cmd/govulncheck@latest \ && mv /go/bin/govulncheck /bin/govulncheck ARG GOIMPORTS_VERSION RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install golang.org/x/tools/cmd/goimports@${GOIMPORTS_VERSION} \ && mv /go/bin/goimports /bin/goimports -ARG DEEPCOPY_VERSION -RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install github.com/siderolabs/deep-copy@${DEEPCOPY_VERSION} \ - && mv /go/bin/deep-copy /bin/deep-copy +ARG GOFUMPT_VERSION +RUN go install mvdan.cc/gofumpt@${GOFUMPT_VERSION} \ + && mv /go/bin/gofumpt /bin/gofumpt # tools and sources FROM tools AS base WORKDIR /src -COPY ./go.mod . -COPY ./go.sum . +COPY go.mod go.mod +COPY go.sum go.sum +RUN cd . RUN --mount=type=cache,target=/go/pkg go mod download RUN --mount=type=cache,target=/go/pkg go mod verify COPY ./kmsg.go ./kmsg.go @@ -67,28 +72,32 @@ RUN FILES="$(gofumpt -l .)" && test -z "${FILES}" || (echo -e "Source code is no # runs goimports FROM base AS lint-goimports -RUN FILES="$(goimports -l -local github.com/siderolabs/go-kmsg .)" && test -z "${FILES}" || (echo -e "Source code is not formatted with 'goimports -w -local github.com/siderolabs/go-kmsg .':\n${FILES}"; exit 1) +RUN FILES="$(goimports -l -local github.com/siderolabs/go-kmsg/ .)" && test -z "${FILES}" || (echo -e "Source code is not formatted with 'goimports -w -local github.com/siderolabs/go-kmsg/ .':\n${FILES}"; exit 1) # runs golangci-lint FROM base AS lint-golangci-lint +WORKDIR /src COPY .golangci.yml . ENV GOGC 50 RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/root/.cache/golangci-lint --mount=type=cache,target=/go/pkg golangci-lint run --config .golangci.yml # runs govulncheck FROM base AS lint-govulncheck +WORKDIR /src RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg govulncheck ./... # runs unit-tests with race detector FROM base AS unit-tests-race +WORKDIR /src ARG TESTPKGS RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg --mount=type=cache,target=/tmp CGO_ENABLED=1 go test -v -race -count 1 ${TESTPKGS} # runs unit-tests FROM base AS unit-tests-run +WORKDIR /src ARG TESTPKGS RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg --mount=type=cache,target=/tmp go test -v -covermode=atomic -coverprofile=coverage.txt -coverpkg=${TESTPKGS} -count 1 ${TESTPKGS} FROM scratch AS unit-tests -COPY --from=unit-tests-run /src/coverage.txt /coverage.txt +COPY --from=unit-tests-run /src/coverage.txt /coverage-unit-tests.txt diff --git a/Makefile b/Makefile index 8995ab4..1a853a5 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,12 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2023-03-17T16:37:39Z by kres 6bde0cc. +# Generated on 2024-01-11T18:39:51Z by kres latest. # common variables SHA := $(shell git describe --match=none --always --abbrev=8 --dirty) TAG := $(shell git describe --tag --always --dirty) +ABBREV_TAG := $(shell git describe --tags >/dev/null 2>/dev/null && git describe --tag --always --match v[0-9]\* --abbrev=0 || echo 'undefined') BRANCH := $(shell git rev-parse --abbrev-ref HEAD) ARTIFACTS := _out WITH_DEBUG ?= false @@ -13,18 +14,20 @@ WITH_RACE ?= false REGISTRY ?= ghcr.io USERNAME ?= siderolabs REGISTRY_AND_USERNAME ?= $(REGISTRY)/$(USERNAME) -GOLANGCILINT_VERSION ?= v1.51.2 -GOFUMPT_VERSION ?= v0.4.0 -GO_VERSION ?= 1.20 -GOIMPORTS_VERSION ?= v0.7.0 -PROTOBUF_GO_VERSION ?= 1.28.1 +PROTOBUF_GO_VERSION ?= 1.31.0 GRPC_GO_VERSION ?= 1.3.0 -GRPC_GATEWAY_VERSION ?= 2.15.2 -VTPROTOBUF_VERSION ?= 0.4.0 +GRPC_GATEWAY_VERSION ?= 2.18.1 +VTPROTOBUF_VERSION ?= 0.5.0 DEEPCOPY_VERSION ?= v0.5.5 +GOLANGCILINT_VERSION ?= v1.55.2 +GOFUMPT_VERSION ?= v0.5.0 +GO_VERSION ?= 1.21.5 +GOIMPORTS_VERSION ?= v0.16.1 GO_BUILDFLAGS ?= GO_LDFLAGS ?= CGO_ENABLED ?= 0 +GOTOOLCHAIN ?= local +GOEXPERIMENT ?= loopvar TESTPKGS ?= ./... KRES_IMAGE ?= ghcr.io/siderolabs/kres:latest CONFORMANCE_IMAGE ?= ghcr.io/siderolabs/conform:latest @@ -37,28 +40,32 @@ PROGRESS ?= auto PUSH ?= false CI_ARGS ?= COMMON_ARGS = --file=Dockerfile +COMMON_ARGS += --provenance=false COMMON_ARGS += --progress=$(PROGRESS) COMMON_ARGS += --platform=$(PLATFORM) COMMON_ARGS += --push=$(PUSH) COMMON_ARGS += --build-arg=ARTIFACTS="$(ARTIFACTS)" COMMON_ARGS += --build-arg=SHA="$(SHA)" COMMON_ARGS += --build-arg=TAG="$(TAG)" +COMMON_ARGS += --build-arg=ABBREV_TAG="$(ABBREV_TAG)" COMMON_ARGS += --build-arg=USERNAME="$(USERNAME)" COMMON_ARGS += --build-arg=REGISTRY="$(REGISTRY)" COMMON_ARGS += --build-arg=TOOLCHAIN="$(TOOLCHAIN)" COMMON_ARGS += --build-arg=CGO_ENABLED="$(CGO_ENABLED)" COMMON_ARGS += --build-arg=GO_BUILDFLAGS="$(GO_BUILDFLAGS)" COMMON_ARGS += --build-arg=GO_LDFLAGS="$(GO_LDFLAGS)" -COMMON_ARGS += --build-arg=GOLANGCILINT_VERSION="$(GOLANGCILINT_VERSION)" -COMMON_ARGS += --build-arg=GOFUMPT_VERSION="$(GOFUMPT_VERSION)" -COMMON_ARGS += --build-arg=GOIMPORTS_VERSION="$(GOIMPORTS_VERSION)" +COMMON_ARGS += --build-arg=GOTOOLCHAIN="$(GOTOOLCHAIN)" +COMMON_ARGS += --build-arg=GOEXPERIMENT="$(GOEXPERIMENT)" COMMON_ARGS += --build-arg=PROTOBUF_GO_VERSION="$(PROTOBUF_GO_VERSION)" COMMON_ARGS += --build-arg=GRPC_GO_VERSION="$(GRPC_GO_VERSION)" COMMON_ARGS += --build-arg=GRPC_GATEWAY_VERSION="$(GRPC_GATEWAY_VERSION)" COMMON_ARGS += --build-arg=VTPROTOBUF_VERSION="$(VTPROTOBUF_VERSION)" COMMON_ARGS += --build-arg=DEEPCOPY_VERSION="$(DEEPCOPY_VERSION)" +COMMON_ARGS += --build-arg=GOLANGCILINT_VERSION="$(GOLANGCILINT_VERSION)" +COMMON_ARGS += --build-arg=GOIMPORTS_VERSION="$(GOIMPORTS_VERSION)" +COMMON_ARGS += --build-arg=GOFUMPT_VERSION="$(GOFUMPT_VERSION)" COMMON_ARGS += --build-arg=TESTPKGS="$(TESTPKGS)" -TOOLCHAIN ?= docker.io/golang:1.20-alpine +TOOLCHAIN ?= docker.io/golang:1.21-alpine # help menu @@ -126,7 +133,8 @@ lint-gofumpt: ## Runs gofumpt linter. .PHONY: fmt fmt: ## Formats the source code @docker run --rm -it -v $(PWD):/src -w /src golang:$(GO_VERSION) \ - bash -c "export GO111MODULE=on; export GOPROXY=https://proxy.golang.org; \ + bash -c "export GOEXPERIMENT=loopvar; export GOTOOLCHAIN=local; \ + export GO111MODULE=on; export GOPROXY=https://proxy.golang.org; \ go install mvdan.cc/gofumpt@$(GOFUMPT_VERSION) && \ gofumpt -w ." @@ -150,7 +158,7 @@ unit-tests-race: ## Performs unit tests with race detection enabled. .PHONY: coverage coverage: ## Upload coverage data to codecov.io. - bash -c "bash <(curl -s https://codecov.io/bash) -f $(ARTIFACTS)/coverage.txt -X fix" + bash -c "bash <(curl -s https://codecov.io/bash) -f $(ARTIFACTS)/coverage-unit-tests.txt -X fix" .PHONY: lint-markdown lint-markdown: ## Runs markdownlint. @@ -162,7 +170,7 @@ lint: lint-golangci-lint lint-gofumpt lint-govulncheck lint-goimports lint-markd .PHONY: rekres rekres: @docker pull $(KRES_IMAGE) - @docker run --rm -v $(PWD):/src -w /src -e GITHUB_TOKEN $(KRES_IMAGE) + @docker run --rm --net=host --user $(shell id -u):$(shell id -g) -v $(PWD):/src -w /src -e GITHUB_TOKEN $(KRES_IMAGE) .PHONY: help help: ## This help menu. diff --git a/go.mod b/go.mod index e2c53d2..d29d4d4 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/siderolabs/go-kmsg -go 1.20 +go 1.21.5 require ( - github.com/siderolabs/gen v0.4.3 - github.com/stretchr/testify v1.8.2 - go.uber.org/goleak v1.2.1 - golang.org/x/sys v0.5.0 + github.com/siderolabs/gen v0.4.7 + github.com/stretchr/testify v1.8.4 + go.uber.org/goleak v1.3.0 + golang.org/x/sys v0.16.0 ) require ( diff --git a/go.sum b/go.sum index 0e6126a..095c7bf 100644 --- a/go.sum +++ b/go.sum @@ -1,27 +1,22 @@ 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= 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/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/siderolabs/gen v0.4.3 h1:V3UsZ2KrsryaTMZGZUHAr1CFdPc2/R1lM6lA4a4zCDo= -github.com/siderolabs/gen v0.4.3/go.mod h1:wS8tFq7sn5vqKAuyS30vJUig3tX5v6q79VG4KfUnILM= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/siderolabs/gen v0.4.7 h1:lM69UYggT7yzpubf7hEFaNujPdY55Y9zvQf/NC18GvA= +github.com/siderolabs/gen v0.4.7/go.mod h1:4PBYMdXxTg292IDRq4CGn5AymyDxJVEDvobVKDqFBEA= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/message.go b/message.go index a9b3b54..54e5a95 100644 --- a/message.go +++ b/message.go @@ -5,6 +5,7 @@ package kmsg import ( + "bytes" "fmt" "strconv" "strings" @@ -81,15 +82,13 @@ type Message struct { // ParseMessage parses internal kernel log format. // // Reference: https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg -func ParseMessage(input string, bootTime time.Time) (Message, error) { - parts := strings.SplitN(input, ";", 2) - if len(parts) != 2 { +func ParseMessage(input []byte, bootTime time.Time) (Message, error) { + prefix, message, ok := bytes.Cut(input, []byte(";")) + if !ok { return Message{}, fmt.Errorf("kernel message should contain a prefix") } - prefix, message := parts[0], parts[1] - - metadata := strings.Split(prefix, ",") + metadata := strings.Split(string(prefix), ",") if len(metadata) < 3 { return Message{}, fmt.Errorf("message metdata should have at least 3 parts, got %d", len(metadata)) } @@ -115,6 +114,84 @@ func ParseMessage(input string, bootTime time.Time) (Message, error) { SequenceNumber: sequence, Clock: clock, Timestamp: bootTime.Add(time.Duration(clock) * time.Microsecond), - Message: message, + Message: unescape(message), }, nil } + +// unescape converts C-style \xXX to byte value, \\ to \, and passes everything else through. +// +//nolint:gocyclo,cyclop +func unescape(message []byte) string { + var b strings.Builder + + b.Grow(len(message)) + + const ( + stateRaw = iota + stateSlash + stateHex1 + stateHex2 + ) + + var ( + hexVal byte + state int + ) + + for _, c := range message { + switch state { + case stateRaw: + if c == '\\' { + state = stateSlash + } else { + b.WriteByte(c) + } + case stateSlash: + switch c { + case 'x': + state = stateHex1 + hexVal = 0 + case '\\': + b.WriteByte('\\') + + state = stateRaw + default: + // invalid escape sequence, ignore + state = stateRaw + } + case stateHex1: + switch { + case c >= '0' && c <= '9': + hexVal = (c - '0') << 4 + state = stateHex2 + case c >= 'A' && c <= 'F': + c += 'a' - 'A' + + fallthrough + case c >= 'a' && c <= 'f': + hexVal = (c - 'a' + 10) << 4 + state = stateHex2 + default: + // invalid escape sequence, ignore + state = stateRaw + } + case stateHex2: + switch { + case c >= '0' && c <= '9': + hexVal |= c - '0' + b.WriteByte(hexVal) + case c >= 'A' && c <= 'F': + c += 'a' - 'A' + + fallthrough + case c >= 'a' && c <= 'f': + hexVal |= c - 'a' + 10 + b.WriteByte(hexVal) + } + + state = stateRaw + } + } + + return b.String() +} diff --git a/message_test.go b/message_test.go index 8e7cc6c..f4207cb 100644 --- a/message_test.go +++ b/message_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/siderolabs/go-kmsg" ) @@ -51,9 +52,20 @@ func TestParseMessage(t *testing.T) { Message: "NET: Registered protocol family 10", }, }, + { + input: `6,339,5140900,-;NET: Registered protocol family \\10\\ in \xc2\xB5s`, + expected: kmsg.Message{ + Facility: kmsg.Kern, + Priority: kmsg.Info, + SequenceNumber: 339, + Clock: 5140900, + Timestamp: mustParse("0001-01-01 00:00:05.1409 +0000 UTC"), + Message: "NET: Registered protocol family \\10\\ in µs", + }, + }, } { - message, err := kmsg.ParseMessage(testCase.input, time.Time{}) - assert.NoError(t, err) + message, err := kmsg.ParseMessage([]byte(testCase.input), time.Time{}) + require.NoError(t, err) assert.Equal(t, testCase.expected, message) } diff --git a/reader.go b/reader.go index 868dab0..52a184d 100644 --- a/reader.go +++ b/reader.go @@ -148,7 +148,7 @@ func (r *reader) scanNoFollow(ctx context.Context, ch chan<- Packet) { } var packet Packet - packet.Message, packet.Err = ParseMessage(string(buf[:n]), r.bootTime) + packet.Message, packet.Err = ParseMessage(buf[:n], r.bootTime) if !channel.SendWithContext(ctx, ch, packet) { return @@ -182,7 +182,7 @@ func (r *reader) scanFollow(ctx context.Context, ch chan<- Packet) { } var packet Packet - packet.Message, packet.Err = ParseMessage(string(buf[:n]), r.bootTime) + packet.Message, packet.Err = ParseMessage(buf[:n], r.bootTime) if !channel.SendWithContext(ctx, ch, packet) { return diff --git a/reader_test.go b/reader_test.go index ca854ed..5d2563c 100644 --- a/reader_test.go +++ b/reader_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/goleak" "github.com/siderolabs/go-kmsg" @@ -33,21 +34,21 @@ func TestReaderNoFollow(t *testing.T) { defer goleak.VerifyNone(t, goleak.IgnoreCurrent()) r, err := kmsg.NewReader() - assert.NoError(t, err) + require.NoError(t, err) defer r.Close() //nolint:errcheck messageCount := 0 for packet := range r.Scan(context.Background()) { - assert.NoError(t, packet.Err) + require.NoError(t, packet.Err) messageCount++ } assert.Greater(t, messageCount, 0) - assert.NoError(t, r.Close()) + require.NoError(t, r.Close()) } func TestReaderFollow(t *testing.T) {