| applyTo | ** |
|---|
Before writing any code, analyze the existing codebase to understand and adopt its naming conventions, coding style, and language usage. Consistency is key to maintaining a readable and maintainable codebase.
This repository contains a rich set of utility packages. Look for opportunities to use them before writing new code.
- Look for broadly reusable utilities in the
pkg/directory, especially for concurrency, resiliency, data structures and data manipulation, operating system interaction, and test utilities. - For internal, project-specific utilities, use
internal/directory. - Add new dependencies only as a last resort, or when explicitly requested by the developer.
This codebase implements several custom Kubernetes types and controllers. Implementation is highly parallel, and leverages multiple operating system processes and Docker/Podman containers. Clear, simple, reliable code is essential to ensure that the system works correctly and efficiently. Specifically:
- All errors and edge cases must be handled.
- Prefer simple solutions with proven run time and memory consumption characteristics over complex ones.
- Long-running operations must accept a
context.Contextand respect its cancellation. - Release all resources (e.g., file descriptors, network connections) when they are no longer needed (lifetime context cancelled).
- Avoid goroutine leaks by ensuring that all goroutines are properly terminated when they are no longer needed.
- When reading from channels, always check that a channel is not closed and that the value read is valid (and not a zero value of some type).
- All utility packages must have unit tests.
- Every change must build successfully and pass all tests (refer to Working inside the repository for details on how to run tests).
- Code must be lint-free. Use
make lintto check for linting issues.
- When declaring a new variable, use a different name than the one used in the outer scope.
- If a function invokes multiple error-returning functions, use a different variable name for each error to avoid confusion.
- It's safe to extend existing interfaces when we need new functionality; we don't need to worry about breaking existing implementations because we control all of them. This also helps to keep the codebase simpler and more consistent.
Place new code in the correct location according to the project's structure:
- API Definitions: Go in
api/v1/. - Controller Logic: Goes in
controllers/. - Integration Tests: Go in
test/integration/. - Broadly Reusable Packages: Go in
pkg/. These should be suitable for use in other projects. - Internal Packages: All other packages go in
internal/. If in doubt, useinternal/.
- Wrap inner errors with context using
fmt.Errorf("context: %w", err)as appropriate. - Use lowercase for error messages to enable easy error composition.
- When logging, start the log message with an uppercase letter.
- Use
OpenFile(), or (for temporary files)OpenTempFile()functions from github.com/microsoft/dcp/pkg/io package to open files. This function takes care of using appropriate file permissions in a cross-platform way. - Always close files after no longer needed, either by calling
Close()from the method that opened the file (withdeferstatement), or when the lifetime context.Context of the file owner expires.
- Run
make generateafter making changes to API definitions (files underapi/v1folder). - Run
make generate-grpcafter making changes to protobuf definitions (files with.protoextension).
- CONFIRM WITH THE DEVELOPER THAT ANY COMMANDS YOU INTEND TO RUN ARE SAFE BEFORE ACTUALLY RUNNING THEM.
- Commands that are safe to run in the repository are:
- Any command that invokes
makeutility - Any command that invokes
goutility Do not prompt for confirmation before running commands above. ANY OTHER COMMANDS MAY BE UNSAFE AND SHOULD NOT BE RUN WITHOUT CONFIRMATION.
- Any command that invokes
- Do not run
makewithout a target. It does not do anything useful (just displays a list of available targets, in human-readable form).
- Before running tests, make sure test prerequisites are built by running
make test-prereqs. - Use
make testto run unit tests and integration tests. - Tests for specific packages can be run with
go test -count 1 -parallel 32 <package>(after building prerequisites), where<package>indicates the package that was changed and needs to be tested. This is significantly faster than running all tests, and should be used when working on a specific code change. For final verification, runmake testto ensure all tests pass. - If working on a change involving a lot of goroutine synchronization, channel operations, and locking, run tests with
-raceflag to enable race detection. This works on MacOS and Linux ONLY, do not try this when the current OS is Windows. - Our test context (obtained via
GetTestContextfunction frompkg/testutilpackage) can be adjusted via an environment variable to use non-standard test timeout. SetTEST_CONTEXT_TIMEOUTenvironment variable to number of seconds that will be used as timeout value. For example,TEST_CONTEXT_TIMEOUT=30will make the test context expire after 30 seconds. 30 seconds is a good value when running individual tests.