Skip to content

Latest commit

 

History

History
365 lines (256 loc) · 17.1 KB

File metadata and controls

365 lines (256 loc) · 17.1 KB

Contributing to Hermeto

Table of contents

How to start a contribution

The team always encourages early communication for all types of contributions. Found a bug or see something that could be improved? Open an issue. Want to address something bigger like adding new package manager support or overhauling the entire project? Open an issue or start a Discussion on Github. This way, we can give you guidance and avoid your work being wasted on an implementation which does not fit the project's scope and goal.

Alternatively, submit a pull request with one of the following

  • A high-level design of the feature, highlighting goals and key decision points.
  • A proof-of-concept implementation.
  • In case the change is trivial, you can start with a draft or even provide a PR with the final implementation.

When working on a pull request please make sure that you have followed pull request guidelines. For AI-assisted contributions, make sure to comply with the AI Contribution Policy. Please consult Development section, it contains a lot of helpful information which will make contributing fast and pleasant process.

How we deal with larger features

Implementing a larger feature (such as adding a new package manager) is usually a very long and detailed effort. This type of work does not fit well into a single pull request; after several comment threads it becomes almost unmanageable (for you) and very hard to review (for us). For that reason, we request the following:

  • Submit a design document that supplements the code. For new package managers, we have a design template that can help guide the implementation.
  • New package managers should mark themselves as "experimental" by adding the x- prefix to its name.
  • Submit small pull requests, with each one implementing a single piece of the overall feature. Experimental features do not need to work end to end, though these should provide warnings/errors for missing functionality when possible.

Note the following:

  • Experimental features are not fully endorsed by the maintainers, and maintainers will not provide support.
  • Experimental features are not production-ready and should never be used in production.
  • Always expect that an experimental feature can be fully dropped from this project without any prior notice.
  • A feature toggle is needed to allow users to opt-in. This is currently handled by prefixing the package manager name with x- (e.g."type": "x-foo" instead of "type": "foo").
  • All SBOMs produced when an experimental feature is used will be marked as such.

If, for some reason, you feel this proposed workflow does not fit the feature you're contributing, please reach out to the maintainers so we can provide an alternative.

Making experimental features production-ready

When a feature's development has reached a stable point, you can propose making it an official part of the project. This signals to users that the feature is production-ready. To communicate this intent to the maintainers, open a pull request containing an Architecture Decision Record (ADR) with an outline of the implementation, and a clear statement of all decisions which were made (as well as their rationale).

Once maintainers are confident that they have enough information to maintain the new feature as officially supported they will accept it and help with moving it out from under experimental flag.

Hermeto's Ethos

Whenever adding a new feature to Hermeto, it is important to keep these fundamental aspects in mind

  1. Report prefetched dependencies as accurately as possible

    Hermeto's primary goal is to prefetch content and enable hermetic builds. But hermetic builds are only useful if they end up providing a more accurate SBOM than a non-hermetic build would. Hermeto strives to download only what's explicitly declared in a project's source code, and accurately report it in the resulting SBOM.

  2. Avoid arbitrary code execution

    Some package manager implementations rely on third-party tools to gather data or even for fetching dependencies. This brings the risk of arbitrary code execution, which opens the door for random things to be part of the prefetched content. This undermines the accuracy of the SBOM, and must be avoided at all costs.

  3. Always perform checksum validation

    The content provided to the build will only be safe if all of the downloaded packages have their checksums verified. In case a mismatch is found, the entire request must be failed, since the prefetched content is tainted and is potentially malicious. There are two types of checksums: server-provided and user-provided. Hermeto prefers but does not require the latter. Every dependency which does not have a user-provided checksum verified, must be clearly marked as such in the resulting SBOM (e.g. see 'pip' support). All dependencies must have at least one checksum in order to be considered validated.

  4. Favor reproducibility

    Always use fully resolved lockfiles or similar input files to determine what content needs to be download for a specific project (e.g. npm's package-lock.json, a pip-compile generated requirements.txt, etc). Resolving the dependencies during the prefetch will prevent its behavior from being deterministic—in other words, the same repository and the same commit hash should always result in identical prefetch results.

Development

Virtual environment

Set up a virtual environment that has everything you will need for development:

python3 -m venv venv
venv/bin/pip install --upgrade pip
venv/bin/pip install -r requirements-extras.txt
venv/bin/pip install -e .

This installs the Hermeto CLI in editable mode, which means changes to the source code will reflect in the behavior of the CLI without the need for reinstalling.

You may need to install Python 3.10 in case you want to test your changes against Python 3.10 locally before submitting a pull request.

dnf install python3.10

The CLI also depends on the following non-Python dependencies:

dnf install golang-bin git

Experimental features

Use x-<pkg> in the request JSON to enable an experimental package manager (positional argument, not a CLI flag). Example: hermeto fetch-deps --source . '[{"type": "x-foo"}]' Please refer to other existing package managers to see how they're enabled and wired to the CLI.

From a user interface perspective, for CLI invocation, the only difference between regular and experimental package managers is the x- prefix in the type name. However experimental package managers are not fully supported which means that the functionality is provided as a technical preview, with no stability, correctness, or backwards compatibility guarantees. Experimental package managers must always be considered a work in progress and never be relied upon in production.

Note: --dev-package-managers is deprecated (no-op) and will be removed; use x-<pkg>.

Coding standards

Hermeto's codebase relies on Ruff & other code quality tools to conform to common and widely used standards:

  • Ruff for formatting, import sorting, and linting
  • mypy for type-checking. Please include type annotations for new code.
  • pytest to run unit tests and report coverage stats. Please aim for (near) full coverage of new code.

Options for all the tools are configured in pyproject.toml.

Run all the checks that your pull request will be subjected to:

nox

Pull request guidelines

Observe the following guidelines when submitting a pull request for review

Do:

  • Write clear and informative commit messages, explain the "WHY"
  • Use PR description to provide further explanation or context about the PR topic
  • Sign off all your commits (DCO)
  • Use your full name and a valid, deliverable email address for commit authorship. The notion of a "known identity" is open to interpretation. We simply require a real full name rather than a pseudonym or handle (sorry, no anonymous contributions). The same applies to your DCO sign-off. If you set your user.name and user.email git configuration options, you can sign your commits automatically with git commit -s
  • Split changes into several self-contained commits
  • Rebase frequently
  • Amend existing commits rather than add fixup commits during review
  • Run linters & unit tests before opening a PR
  • Ensure every single commit passes CI

Don't:

  • Use gitmojis anywhere, they only add noise
  • Squash all changes into a single massive commit unless it is a simple or self-contained change

Tips:

  • Use GitHub comments to further clarify background for the implementation if needed
  • Use diagrams, sample code, and links to specific parts of external documentation
  • Respond to review comments - responding with "Done" is often enough to indicate that a thread can be resolved
  • Resolve trivial (e.g. nitpicks) review threads yourself, leave the rest to the reviewers

Error message guidelines

We try to keep error messages friendly and actionable.

  • If there is a known solution, the error message should politely suggest the solution
    • Include a link to the documentation when suitable
  • If there is no known solution, suggest where to look for help
  • If retrying is a possible solution, suggest retrying and where to look for help if the issue persists

The error classes aim to encourage these guidelines. See the errors.py module.

Comment guidelines

In general, consider adding comments to the code whenever there exists any context which is not obvious from the code alone. When writing a comment do not repeat how a piece of code works, do explain why this is needed.

If your code was inspired by any third-party sources, consider adding a comment with a link to these sources.

Test guidelines

When extending an existing feature, please add a new test case instead of modifying any existing ones. Large test scenarios with many branching paths are very hard to understand and to maintain. It is ok to copy and paste large parts of an existing test if needed for a new scenario. It is also fine to add a new parameter group to an existing test, as long as the test function remains unchanged.

Running unit tests

Run all unit tests (but no other checks):

nox -s python-3.10

For finer control over which tests get executed, e.g. to run all tests in a specific file, run:

nox -s python-3.10 -- tests/unit/test_cli.py

Even better, run it stepwise (exit on first failure, re-start from the failed test next time):

nox -s python-3.10 -- tests/unit/test_cli.py --stepwise

You can also run a single test class or a single test method:

nox -s python-3.10 -- tests/unit/test_cli.py::TestGenerateEnv
nox -s python-3.10 -- tests/unit/test_cli.py::TestGenerateEnv::test_invalid_format
nox -s python-3.10 -- tests/unit/extras/test_envfile.py::test_cannot_determine_format

In short, nox passes all arguments to the right of -- directly to pytest.

Generating new test data

Whenever a particular package manager supported version is bumped it is considered good practice to also re-generate the mocked unit test data using that version of package manager (make sure you have it available in path). You do that by executing the corresponding script under hack/mock-unittest-data. Note that there may be times the data has to be re-generated even without bumping the backend version, e.g. we added support for a particular feature of the package manager which we didn't add at the time of the initial support release.

Example:

hack/mock-unittest-data/gomod.sh

To generate new data (output, dependencies checksums, vendor checksums) and run integration tests with them:

nox -s generate-test-data

Generate data for test cases matching a pytest pattern:

nox -s generate-test-data -- -k test_e2e_gomod

Running integration tests

Build Hermeto image (localhost/hermeto:latest) and run most integration tests:

nox -s integration-tests

Build Hermeto image (localhost/hermeto:latest) and run all integration tests:

nox -s all-integration-tests

Note: while developing, you can run the Nexus server with tests/nexusserver/run.sh.

To run integration-tests with custom image, specify the HERMETO_TEST_IMAGE environment variable or use the --hermeto-image option. Examples:

HERMETO_TEST_IMAGE=ghcr.io/hermetoproject/hermeto:{tag} nox -s integration-tests
HERMETO_TEST_IMAGE=localhost/hermeto:latest nox -s integration-tests

nox -s integration-tests -- --hermeto-image=ghcr.io/hermetoproject/hermeto:{tag}
nox -s integration-tests -- --hermeto-image=localhost/hermeto:latest

Another way to run integration tests is directly via pytest. This approach can be helpful for debugging and gives you more control over which tests are run and how they are executed. Examples:

HERMETO_TEST_IMAGE=localhost/hermeto:latest pytest [path/to/integration/test]

pytest --hermeto-image=localhost/hermeto:latest [path/to/integration/test]

All hermeto integration test CLI options as well as the corresponding environment variables can be listed with pytest --help.

To run integration tests in parallel, use the -n option + the number of workers. To utilize all CPU cores, use auto as the number of workers. Examples:

nox -s integration-tests -- -n 4
nox -s integration-tests -- -n auto

pytest [path/to/test] -n 4
pytest [path/to/test] -n auto

Adding new dependencies to the project

Sometimes when working on adding a new feature you may need to add a new dependency to the project. Usually, one commonly goes about it by adding the dependency to one of the requirements files or the more modern and standardized pyproject.toml file. In our case, dependencies must always be added to the pyproject.toml file as the requirements files are generated by the pip-compile tool to not only pin versions of all dependencies but also to resolve and pin transitive dependencies. Since our pip-compile environment is tied to Python 3.10, we have a Makefile target that runs the tool in a container image so you don't have to install another Python version locally just because of this. To re-generate the set of dependencies, run the following in the repository and commit the changes:

nox -s pip-compile

Troubleshooting

Below are common issues that you may encounter while developing your contribution to Hermeto.

Unable to compile createrepo-c wheel

In some circumstances your system may try to compile the createrepo-c Python wheel from source code. Unfortunately this requires several C-headers to be present on your system, which can be challenging to chase down.

This situation can usually be resolved in one of two ways:

  1. Check if a new version of the createrepo-c wheel has been published. If yes, then update the dependency per instructions above.
  2. Otherwise add the required C dependencies to your system. Below are the package names used by Fedora with the necessary dependencies. Note that package names may not be the same on other Linux distributions:
    • bzip2-devel (provides bzip2 C headers)
    • cmake
    • gcc (C compiler)
    • glib2-devel
    • libmodulemd-devel
    • libzstd-devel
    • ninja-build (build tool)
    • python3-devel (Python development headers)
    • rpm-devel
    • sqlite-devel

On Fedora systems, these can be installed using dnf:

sudo dnf install -y bzip2-devel cmake gcc glib2-devel libmodulemd-devel libzstd-devel ninja-build python3-devel rpm-devel sqlite-devel

Releasing

To release a new version of Hermeto, simply create a GitHub release. Note that Hermeto follows semantic versioning rules.

Release schedule

This project follows a weekly release schedule, with planned releases every Tuesday. If there is no significant content to ship on a given release day, we may skip it. Urgent bug and security fixes are released promptly as needed at the team’s discretion. Should we encounter any CI issues on the day of a release, we either release immediately after the issue is resolved or skip the release based on the nature of the changes (urgent vs regular).