diff --git a/.github/workflows/protos.yml b/.github/workflows/protos.yml new file mode 100644 index 00000000..8519bde6 --- /dev/null +++ b/.github/workflows/protos.yml @@ -0,0 +1,29 @@ +name: Protos + +on: + pull_request: + paths: + - 'pkg/protocol/protobuf/**' + - 'Dockerfile.protoc' + - 'Makefile' + - '.github/workflows/protos.yml' + push: + branches: [main] + paths: + - 'pkg/protocol/protobuf/**' + - 'Dockerfile.protoc' + - 'Makefile' + - '.github/workflows/protos.yml' + +permissions: + contents: read + +jobs: + check: + name: Verify generated protos are up to date + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run proto-check + run: make proto-check diff --git a/Dockerfile.protoc b/Dockerfile.protoc new file mode 100644 index 00000000..afb72650 --- /dev/null +++ b/Dockerfile.protoc @@ -0,0 +1,44 @@ +# Dockerfile.protoc — reproducible protoc + protoc-gen-go toolchain. +# +# The versions below are pinned to match the headers of the committed +# *.pb.go files in pkg/protocol/protobuf/ (the runtime version printed +# by protoc is "3.21.9", which corresponds to the upstream release +# tag "v21.9"). Bumping either ARG will typically produce a diff in the +# regenerated code; run `make proto-gen-docker` and commit the result +# in the same change. + +FROM golang:1.23.0-bookworm AS toolchain + +ARG PROTOC_VERSION=21.9 +ARG PROTOC_GEN_GO_VERSION=1.28.1 +# SHA256 sums for the official protoc release archives. Update these when +# bumping PROTOC_VERSION; values can be recomputed with `sha256sum` against +# the assets at https://github.com/protocolbuffers/protobuf/releases. +ARG PROTOC_SHA256_X86_64=3cd951aff8ce713b94cde55e12378f505f2b89d47bf080508cf77e3934f680b6 +ARG PROTOC_SHA256_AARCH_64=a584286dfa8ebb17032ece206ed74d5e9931e2edb9016e427be2a0dab3b21071 +ARG TARGETARCH + +RUN apt-get update \ + && apt-get install --no-install-recommends -y curl unzip ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# protoc release archives use x86_64 / aarch_64 in their file names. +RUN case "${TARGETARCH:-amd64}" in \ + amd64) PROTOC_ARCH=x86_64; PROTOC_SHA256="${PROTOC_SHA256_X86_64}" ;; \ + arm64) PROTOC_ARCH=aarch_64; PROTOC_SHA256="${PROTOC_SHA256_AARCH_64}" ;; \ + *) echo "unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ + esac \ + && curl -fsSL -o /tmp/protoc.zip \ + "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip" \ + && echo "${PROTOC_SHA256} /tmp/protoc.zip" | sha256sum -c - \ + && unzip -q /tmp/protoc.zip -d /usr/local/protoc \ + && rm /tmp/protoc.zip \ + && chmod +x /usr/local/protoc/bin/protoc + +RUN go install "google.golang.org/protobuf/cmd/protoc-gen-go@v${PROTOC_GEN_GO_VERSION}" + +ENV PATH="/usr/local/protoc/bin:/go/bin:${PATH}" + +WORKDIR /workspace + +ENTRYPOINT ["protoc"] diff --git a/Makefile b/Makefile index 72efa2e3..9a8b8464 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,13 @@ endif PROTO_DIR=./pkg/protocol/protobuf PROTO_FILES=$(wildcard $(PROTO_DIR)/*.proto) +# Pinned protoc toolchain. The protoc and protoc-gen-go versions (and the +# protoc archive checksums) are pinned in Dockerfile.protoc, which is the +# single source of truth. Expect a diff in the regenerated *.pb.go files +# when bumping them. +PROTOC_IMAGE=vehicle-command-protoc +PROTOC_DOCKERFILE=Dockerfile.protoc + all: build linters: @@ -38,4 +45,41 @@ doc-images: proto-gen: $(PROTO_FILES) protoc --proto_path $(PROTO_DIR) --go_out $(PROTO_DIR) --go_opt=module=github.com/teslamotors/vehicle-command/pkg/protocol/protobuf ${PROTO_FILES} -.PHONY: install build linters test format set-version doc-images protoc-gen +# Build the pinned protoc toolchain image. Cached layers make this fast +# when Dockerfile.protoc has not changed. +proto-builder: + docker build \ + -f $(PROTOC_DOCKERFILE) \ + -t $(PROTOC_IMAGE) \ + . + +# Regenerate *.pb.go using the pinned toolchain. Use this instead of +# `proto-gen` to get reproducible output independent of the host's +# installed protoc version. +proto-gen-docker: proto-builder + docker run --rm \ + --user "$$(id -u):$$(id -g)" \ + -v "$(CURDIR):/workspace" \ + -w /workspace \ + $(PROTOC_IMAGE) \ + --proto_path $(PROTO_DIR) \ + --go_out $(PROTO_DIR) \ + --go_opt=module=github.com/teslamotors/vehicle-command/pkg/protocol/protobuf \ + $(PROTO_FILES) + +# Regenerate via Docker and fail if the result differs from what is +# committed, or if regeneration produced files that are not tracked (a new +# .proto whose .pb.go was never committed). Intended for CI to catch +# out-of-date or missing *.pb.go files. +proto-check: proto-gen-docker + @if ! git diff --quiet -- $(PROTO_DIR) || \ + [ -n "$$(git ls-files --others --exclude-standard -- $(PROTO_DIR))" ]; then \ + echo "ERROR: generated protobuf files are out of date or missing."; \ + echo "Run 'make proto-gen-docker' and commit the result."; \ + git --no-pager status --short -- $(PROTO_DIR); \ + git --no-pager diff -- $(PROTO_DIR); \ + exit 1; \ + fi + @echo "Generated protobuf files are up to date." + +.PHONY: install build linters test format set-version doc-images proto-gen proto-builder proto-gen-docker proto-check