Skip to content

Latest commit

 

History

History
296 lines (203 loc) · 14.9 KB

CONTRIBUTING.md

File metadata and controls

296 lines (203 loc) · 14.9 KB

Contributing to Cachi2

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. 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 that larger features be split into a series of pull requests. Once approved, these pull requests will be merged into "main", but the new feature will be marked as experimental, and will retain this mark until it meets code quality standards and all necessary changes are merged.

This has several implications

  • 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 being handled by the dev-package-managers flag.
  • 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.

Cachi2's Ethos

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

  1. Report prefetched dependencies as accurately as possible

    Cachi2'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. Cachi2 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. Cachi2 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 reproducibilty

    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:

make venv
source venv/bin/activate

This installs the Cachi2 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.9 in case you want to test your changes against Python 3.9 locally before submitting a pull request.

dnf install python3.9

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

dnf install golang-bin git

You should now have everything needed to try out the CLI or hack on the code in vim your favorite editor.

Developer flags

  • --dev-package-managers (hidden): enables in-development package manager(s) for test. Please refer to other existing package managers to see how they're enabled and wired to the CLI.

    Invoke it as cachi2 fetch-deps --dev-package-managers FOO

    More explicitly

    • --dev-package-managers is a flag for fetch-deps
    • FOO is an argument to fetch-deps (i.e. the language to fetch for)

Coding standards

Cachi2's codebase conforms to standards enforced by a collection of formatters, linters and other code checkers:

  • black (with a line-length of 100) for consistent formatting
  • isort to keep imports sorted
  • flake8 to (de-)lint the code and politely ask for docstrings
  • 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 and tox.ini.

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

make test

Pull request guidelines

Observe the following guidelines when submitting a pull request for review

  • Write clear and informative commit messages. If you want to provide further context, use the PR's description
  • Sign off on all commits
  • Please use the PR's description to provide further explanation of the pull request's title
  • Split changes into multiple commits such that each commit addresses a clear and concise problem
  • Avoid PRs which are too large — split the work into multiple PRs if necessary
  • Amend existing commits rather than add new commits to fix issues introduced in the same pull request
  • Keep your branch up-to-date using rebase — we don't use merge commits
  • Verify the coding standards by running the configured linters
  • Ensure that every single commit passes CI — this is mandatory
  • Feel free to use Github comments to clarify implementation points and consider adding the same comments to the code
  • Feel free to use diagrams, sample code, or links to specific parts of external documentation — they are highly encouraged
  • Please respond to inline comments made in pull requests: it makes it easier to track what has been done and what has not and speeds up review process. "Done" is often enough to indicate that you have reacted to a comment
  • For trivial reviewers' requests it is ok to implement it, respond with "Done" and resolve the thread.

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):

make test-unit

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

tox -e py39 -- tests/unit/test_cli.py

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

tox -e py39 -- tests/unit/test_cli.py --stepwise

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

tox -e py39 -- tests/unit/test_cli.py::TestGenerateEnv
tox -e py39 -- tests/unit/test_cli.py::TestGenerateEnv::test_invalid_format
tox -e py39 -- tests/unit/extras/test_envfile.py::test_cannot_determine_format

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

Running integration tests

Build Cachi2 image (localhost/cachi2:latest) and run most integration tests:

make test-integration

Run tests which requires a local PyPI server as well:

make test-integration TEST_LOCAL_PYPISERVER=true

Note: while developing, you can run the PyPI server with tests/pypiserver/start.sh &.

To run integration-tests with custom image, specify the CACHI2_IMAGE environment variable. Examples:

CACHI2_IMAGE=quay.io/redhat-appstudio/cachi2:{tag} tox -e integration
CACHI2_IMAGE=localhost/cachi2:latest tox -e integration

Similarly to unit tests, for finer control over which tests get executed, e.g. to run only 1 specific test case, execute:

tox -e integration -- tests/integration/test_package_managers.py::test_packages[gomod_without_deps]

Running integration tests and generating new test data

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

make GENERATE_TEST_DATA=true test-integration

Generate data for test cases matching a pytest pattern:

CACHI2_GENERATE_TEST_DATA=true tox -e integration -- -k gomod

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.9, 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:

make pip-compile

Releasing

To release a new version of Cachi2, simply create a [GitHub release][cachi2-releases]. Note that Cachi2 follows semantic versioning rules.

Upon release, the .tekton/release.yaml pipeline tags the corresponding [Cachi2 image][cachi2-container] with the newly released version tag (after validating that the tag follows the expected format: $major.$minor.$patch, without a v prefix).

You apply a release tag to a specific commit. The .tekton/push.yaml pipeline should have built the image for that commit already. This is the "corresponding image" that receives the new version tag. If the image for the tagged commit does not exist, the release pipeline will fail.

You can watch the release pipeline in the [OpenShift console][ocp-cachi2-pipelines] in case it fails (the pipeline is not visible anywhere in GitHub UI). For intermittent failures, retrying should be possible from the OpenShift UI or by deleting and re-pushing the version tag.

⚠ The release pipeline runs as soon as you push a tag into the repository. Do not push the new version tag until you are ready to publish the release. You can use GitHub's ability to auto-create the tag upon publishment.

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).