blob: 1fc00ab1ede2ffbf3967cc91785a8e067927200f [file] [view]
# Gemini Code-Assist Context for the LUCI Go Repository
This document provides essential context for AI models interacting with the
`luci-go` codebase.
## 1. Project Overview
The `luci-go` repository contains the Go source code for the Layered Universal
Continuous Integration (LUCI) system.
## 2. Key Technologies and Libraries
- **Go:** The primary programming language. The specific version is defined in
the `go.mod` file.
- **Protocol Buffers (Protobuf):** Used for defining data structures and service
interfaces.
- **pRPC and gRPC:** The primary RPC framework for communication between
services.
- **Google Cloud Platform (GCP):** The primary deployment target, with extensive
use of services like Cloud Spanner, Cloud Tasks, Cloud Storage, BigQuery,
Cloud Pub/Sub, Cloud Scheduler. While Cloud Run is the preferred runtime for
new services, most still run on AppEngine or Google Kubernetes Engine.
- **Depot Tools:** The primary toolchain for managing the repository, including
code reviews and presubmit checks.
- **Golangci-lint:** Used for linting Go code. Configuration is in the
`.golangci.yaml` files located in each service's directory.
- **Staticcheck:** Used for static analysis of Go code. Configuration is in the
`staticcheck.conf` file.
## 3. Project Structure and Conventions
### Key Directories
The repository is organized into a series of top-level directories, each
corresponding to a specific LUCI service or component. For example, the
`buildbucket` directory contains the source code for the Buildbucket service,
and the `auth` directory contains the authentication libraries.
There are also folders which contain key shared code, notably:
- `common/`: Contains common libraries and utilities shared across multiple
services.
- `server/`: Contains common LUCI server framework code. This framework takes
care of many common concerns that would not be handled by net/http, like:
SIGTERM handling/graceful draining, request deadlines, panic catching, load
shedding, monitoring, health-checking, authorisation of Cloud Scheduler/Tasks
pushes.
The main UI for LUCI is served by MILO which can be found in `milo/` directory.
The frontend code lives in `milo/ui/`; see `milo/ui/GEMINI.md` for details
navigating this part of the repo.
### Code Conventions
#### Formatting
Go code is formatted using `gofmt`.
### Linting
Code is linted using `golangci-lint` and `staticcheck`.
### Testing
Tests are written using the standard Go testing framework. Test
files are named with the `_test.go` suffix. Tests use the the
`go.chromium.org/luci/common/testing/ftt` library (see
`common/testing/ftt/doc.go`) for nesting test cases and the `assert` library
from `go.chromium.org/luci/common/testing/truth/assert` for assertions.
See `resultdb/internal/services/recorder/create_root_invocation_test.go` for
an example how these libraries are typically used.
### API Surface
When modifying the API surface, the following style guide applies:
- **API Surface/Protos:** We follow the Google AIPs (API Improvement Proposals)
available at https://google.aip.dev/{NUMBER}. This includes:
- AIP-121: Resource-oriented design
- AIP-122: Resource names
- AIP-131: Standard methods: Get
- AIP-132: Standard methods: List (an implementation of the AIP-132 order_by
clause is available at `common/data/aip132/`)
- AIP-133: Standard methods: Create
- AIP-134: Standard methods: Update
- AIP-135: Standard methods: Delete
- AIP-136: Custom methods
- AIP-140: Field names
- AIP-154: Resource freshness validation (etags for race-free updates)
- AIP-155: Request identification (for retry-safe Create/Update RPCs)
- AIP-157: Partial responses (for views/field masks)
- AIP-158: Pagination
- AIP-160: Filtering (an implementation of an AIP-160 filter-parser in go is
available at `common/data/aip160/`).
- AIP-210: Unicode
- AIP-211: Authorization checks
- AIP-231: Batch methods: Get
- AIP-233: Batch methods: Create
- **Request validation:** ALL fields (except OUTPUT_ONLY fields - see AIP-203)
in the request should be validated by the server. Validation should comprise
at MINIMUM:
- Verifying a value is set, if it must be set.
- If the field is of a type that allows variable-length data (e.g. a string,
bytes or a repeated type), the field is within an allowed length limit (if
no length limit exists, one should be created and documented on the proto).
In addition to the per-field length limit, a length limit may sometimes also
be applied at a higher-level, such as on the total size of a test result or
a set of properties. This can be enforced by setting a limit on the size as
measured by proto.Size().
- If the value is a string, that it comes from a known alphabet. At minimum,
strings should be valid UTF-8 and be in Unicode Normal Form C. In most cases
they should also be limited to printable Unicode characters only. See
AIP-210 - Unicode. For identifiers, it is common to restrict to a narrow set
of inputs such as `[a-z][a-zA-Z0-9]*`, which takes care of most of the
checks above.
- It is generally preferable that fields are validated in the order that they
are specified in the proto, to make it easy to verify that all fields have
in fact been validated.
- **Errors construction:**
- Validation errors for an invalid request input should be annotated with the
path of the problematic input. For example,
`books: [0]: my_description: non-printable character at byte index 2` for a
validation issue with the field books[0].my_description. Typically this is
achieved by a hierarchy of validation methods which annotate one part of the
path each, e.g. ValidateRequest(request) calls ValidateBooks(request.books)
and annotates any errors with `errors.Fmt("books: %w", err)`, ValidateBooks
calls ValidateBook(books[0]) and annotates its error (if any) with
`errors.Fmt("[0]: %w")`, and so on.
- For monitoring purposes, API usability, and to ensure clients can correctly
implement retry logic, it is important that all errors that are not internal
server errors (codes.Internal) are annotated with the correct status code.
This allows separating client from server errors, and retriable from
non-retriable (permanent) errors. Services can achieve this by ensuring
errors that are not internal server errors are annotated with the
appropriate code using the appstatus package:
For example (using `go.chromium.org/luci/grpc/appstatus`):
`appstatus.Errorf(codes.NotFound, "book %q not found", bookName)`
- **Tests:** All RPCs exposed by a service must have associated tests to ensure
required access controls are in place, invalid inputs are rejected, and both
normal and non-normal situations are correctly handled.
- At minimum the following tests should exist:
- For security, a test to validate the service enforces authorisation checks
(e.g. denies access to callers without the required access). These should
also ensure the returned authorisation errors are understandable (i.e.
describes the missing permissions or group membership) and have the
correct status code (codes.PermissionDenied).
- Tests to validate the service validates the request input, including each
field (that is not OUTPUT_ONLY). These should ensure returned errors have
the correct field path (see above), an understandable human-readable error
and are annotated with codes.InvalidArgument.
- Tests to validate each non-normal path (e.g. request valid but entity does
not exist). Each such test should again ensure the presence of the correct
status code.
- Tests to validate the happy path.
- When validating a response that is expected to error, both the error message
and code should be checked.
If the appstatus error has already been converted to a grpc code in the
service postlude using `appstatus.GRPCifyAndLog(ctx, err)`, the code can be
asserted using:
`assert.That(t, err, grpccode.ShouldBe(codes.NotFound))`
Where grpccode is `go.chromium.org/luci/grpc/grpcutil/testing/grpccode`.
Alternatively for standalone methods that contribute to an RPC response,
codes can be asserted using an assertion like:
`assert.That(t, appstatus.Code(err), should.Equal(codes.NotFound))`
- Test cases should try and keep cause and effect clear by factoring out
common/repeated test setup. The ideal test case changes only the properties
the test is trying to exercise and asserts the expected response.
- A contemperanous (but not perfect) example of how the above can be achieved
is: `resultdb/internal/services/recorder/create_root_invocation_test.go`.
## 4. Build and Test
### Build
The project is built using the standard Go toolchain.
As builds of the entire repository can take some time, it is best to only
build the directory you need, e.g. if working on ResultDB:
`go build go.chromium.org/luci/resultdb/...`
### Protocol buffers bindings
After changing protos, regenerate proto buffer go bindings by running
`go generate` against the directory, for example:
`go generate go.chromium.org/luci/resultdb/proto/v1`
### Test
Tests are run using the `go test` command. As most tests are integration
tests, you must set the `INTEGRATION_TESTS=1` environment variable to run them.
For example, to run all tests under resultdb/, run the following command:
`INTEGRATION_TESTS=1 go test go.chromium.org/luci/resultdb/...`
Running all tests in `go.chromium.org/luci/...` takes a long time, so it is best
to run only the directory you need.
## 5. Contribution and Code Review
Changes from the current git branch are uploaded for review using the
`git cl upload` command (`git cl` is provided by the `depot_tools` repository).
Git commit messages follow a format like:
```
[ResultDB] Add validation for BatchCreateTestResults RPC.
A longer description appears here.
BUG=b:{BUG_NUMBER}
TEST=INTEGRATION_TESTS=1 go test go.chromium.org/luci/resultdb/...
```