8000 Add purls (Package URLs) to `PackageRecord` by baszalmstra · Pull Request #63 · conda/ceps · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add purls (Package URLs) to PackageRecord #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

baszalmstra
Copy link
Contributor

This CEP describes a change to the PackageRecord format and the corresponding repodata.json file to include purls (Package URLs of repackaged packa 10000 ges to identify packages across multiple ecosystems.

rendered

@wolfv
Copy link
Contributor
wolfv commented Nov 23, 2023

Awesome CEP! :)


## Abstract

This CEP describes a change to the `PackageRecord` format and the corresponding `repodata.json` file to include `purls` (Package Urls) of repackaged packages to identify packages across multiple ecosystems.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a link to the definition of a PackageRecord? I struggle to find an authoritative source for it.

Copy link
Contributor Author
@baszalmstra baszalmstra Nov 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, I believe that atm there is no actual "authorative" source.

There is this relatively old definition of a RepoDataRecord: https://github.com/conda/schemas/blob/main/repodata-record-1.schema.json

There is this new effort to document the schemas better (conda/schemas#26) where it's also called RepoDataRecord: https://github.com/conda/schemas/blob/b143c82a71833570fbe9be2313368b33c0e84726/conda_models/package_record.py#L23

And we have the definition in rattler: https://docs.rs/rattler_conda_types/latest/rattler_conda_types/struct.PackageRecord.html

In rattler (and I believe in conda as well), there is this distinction:

  • PackageRecord: contains all the fields for a single entry in the repodata.json
  • RepoDataRecord: inherits all fields from PackageRecord and adds fields to identify the origin of the data (channel, url, etc.)
  • PrefixRecord: inherits all fields from RepoDataRecord and additionally stores information about how the package was installed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I think the most "official" source for this is https://github.com/conda/conda/blob/e783377439ed1c413c6bffb9b785ae1d79c2392a/conda/models/records.py#L247. That module also offers some sort of definition in the top-level docstring.

baszalmstra added a commit to conda/rattler that referenced this pull request Nov 24, 2023
baszalmstra added a commit to prefix-dev/pixi that referenced this pull request Nov 24, 2023
This PR adds support for checking the satisfiability of the lock-file
which includes pypi-dependencies.

Purls have been added to the lock-file
(conda/rattler#414) (See also:
conda/ceps#63). This enables checking
which conda packages will install which pypi packages without needing to
check the internet. This ensures we can still check if a lock-file is up
to date quickly.

I did not profile this code but I think there are a lot of places we can
improve the performance. Thats for a later PR.

I also didn't add tests. I think we should but we can also do that in
another PR.

Closes #467

---------

Co-authored-by: Ruben Arts <ruben.arts@hotmail.com>
}
```

PURL is already supported by dependency-related tooling like SPDX (see [External Repository Identifiers in the SPDX 2.3 spec](https://spdx.github.io/spdx-spec/v2.3/external-repository-identifiers/#f35-purl)), the [Open Source Vulnerability format](https://ossf.github.io/osv-schema/#affectedpackage-field), and the [Sonatype OSS Index](https://ossindex.sonatype.org/doc/coordinates); not having to wait years before support in such tooling arrives is valuable.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also mention PEP-725 (WIP).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Discourse thread has examples showing how the Spack community wants to use this kind of thing: https://discuss.python.org/t/pep-725-specifying-external-dependencies-in-pyproject-toml/31888/31

cep-purls.md Outdated
* We can keep this information close to the conda package description.
* We can incrementally add `purls` through repodata patches.

The downside is that the (already large) repodata.json file will grow.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we add a separate-yet-adjacent purls.json like we did with run_exports.json in CEP-12?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just include them with sharded repodata but for backwards compatibility it seems logic to go the same route as run exports as well (also allowing patching them).

Copy link
Contributor
@jaimergp jaimergp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea and I will be supportive. Havin this metadata readily available would allow us to be listed in repology.org, for example! It would also play nicely with the (draft) PEP-725 for external metadata in PyPI.

However, I think this CEP right now is talking about serving metadata before we have discussed how to source it, define it and store it.

Whatever ends up in the repodata.json comes, in part, from the info/index.json metadata inside the conda artifact. Then this is augmented with things like sha256 and final size by conda-index (because they cannot be known when the package is being archived).

So before we speak about repodata, we should discuss where in the inner artifact metadata we will store the PURL info. To answer that, we must answer where in the conda-build recipe we will include that information :D

IOW, I'd like to know your thoughts about:

  • Where in the current meta.yaml we should define the PURLs. about seems to be the most obvious one, which means this will probably end up in info/about.json.
  • Whether to serve the PURLs separately in a purls.json or not. I honestly don't think putting it in repodata.json is a good idea. I get that it makes sense if you want to have a canonical link between PyPI in conda-forge so Pixi can solve things nicely. It might also be served in channeldata.json (since most of the time PURLs are tied to the source not the platform-dependent, target artifact).

@jakirkham
Copy link
Member
jakirkham commented May 8, 2024

Would this also help us address Repology's needs for supporting Conda packages ( repology/repology-updater#518 )?

Edit: Nvm missed Jaime has the same idea

@ytausch
Copy link
ytausch commented Oct 14, 2024

Where in the current meta.yaml we should define the PURLs. about seems to be the most obvious one, which means this will probably end up in info/about.json.

I agree that about makes the most sense. However, this adds the redundancy of defining the upstream package twice in the recipe. A more sophisticated solution would be adding a new purl source type for the source section, which gets resolved to a PyPI tarball URL by conda build. The purls for a package could then be automatically inferred from the sources it has been built from. In all cases, a manual option to define the purls likely has to remain for some specific use cases.

While this would facilitate simplicity, avoid redundancy, and avoid errors in the recipe, I see the following downsides with that solution:

  • different package outputs or variants may not actually use all sources that are available, requiring manually overriding the purls or another clever solution for that
  • how to verify the hash of a source tarball is more evident if the source URL is stated explicitly in the recipe
  • to avoid complexity, we could only support a subset of purl types. PyPI is by far the most important IMO. It could confuse people if only a subset of purls are valid sources.
  • introducing a new source type is simply more work than introducing a new about field - especially in related tooling such as cf-scripts or conda-smithy
  • backward compatibility?

Whether to serve the PURLs separately in a purls.json or not. I honestly don't think putting it in repodata.json is a good idea. I get that it makes sense if you want to have a canonical link between PyPI in conda-forge so Pixi can solve things nicely. It might also be served in channeldata.json (since most of the time PURLs are tied to the source not the platform-dependent, target artifact).

I do not have a strong opinion here since I am not too involved with the tools that would need to process that data.

@bollwyvl
Copy link

I think a broader question is whether package-urlcan be adopted more directly by the ecosystem. Relying on "the URL where you get your package" or "what it's called on disk" aren't as effective as an agreed-upon grammar for identifying packages, especially in the "is this CVE relevant to me" scenario.

Brief aside, and likely worth including in the text

A purl is a URL composed of seven components:

 scheme:type/namespace/name@version?qualifiers#subpath

To put this in the context of the above, a given .conda package might claim a PURL as a proxy in one or more other types, but by existing, it should claim one in the conda type. Indeed, a subset is already part of the spec. For example, an old version of django:

It might make sense to advocate for some changes to the conda part of the spec (and test data), namely:

  • update the default channel from r.a.com -> c.a.org
    • then the namespace part of the url would encode the conda channel (e.g. pkg:conda/conda-forge/django)
    • i don't know what the "new null" would be, but pretty sure it can't be defaults
  • use label for e.g. main (not channel, as in the example)

While i don't think much can be done about "where you got the source tarball" (because GitHub sources, etc), I don't think a recipe author should have to calculate all these things... but certainly could given the available data today:

# meta.yaml
{% set version = "1.10.1" %}
package: 
  name: django
  version: {{ version }}
# ...
about:
  # ...
  purls:
    - pkg:pypi/django@{{ version }}
    # this should be fully automated, either at build time (weird?) or trivially-derivable
    - pkg:conda/{{ channel_targets.split(" ")[0] }}/django@1.10.1?subdir={{ target_platform }}&label={{ channel_targets.split(" ")[1] }}&build=py{{ py }}_{{ build_number }}

So the above full purls might expand to

purls:
- pkg:pypi/django@1.10.1
- pkg:conda/conda-forge/django@1.10.1?subdir=win-32&label=main&build=py35_0

@bollwyvl
Copy link
bollwyvl commented Jan 4, 2025

Thinking about this more in the context of "accidental cross-ecosystem namesquatting" on zulip: as pkg: isn't presently a valid package name, the MatchSpec grammar could be expanded to include purls as an alternative package identifier

dependencies:
- pkg:pypi/django >=1.10.1,<1.11

treating everything after the whitespace as "this part is about conda" would still allow for all our variant business, but presumably could eventually be expanded to allow per-ecosystem fields... luckily, pypi only has semi-irrelevant stuff like file_name, but other ecosystems could be more complex.

@ytausch
Copy link
ytausch commented Jan 16, 2025

include purls as an alternative package identifier

I don't follow entirely. What would your example refer to? The PyPI package or the corresponding conda-forge package?

@bollwyvl
Copy link

include purls as an alternative package identifier

I don't follow entirely. What would your example refer to? The PyPI package or the corresponding conda-forge package?

Right, the user wants the corresponding conda package, from the highest-priority channel, but resolved in the parallel namespace.

# e.g. in pixi.toml
[dependencies]
# |  a new package identifier
# V
"pkg:pypi/django" = ">=1.10.1,<1.11"
#                    ^      
#                    |  the conda constraints, in the MatchSpec grammar
"pkg:golang/github.com/rhysd/actionlint" = ">=1.7.7"

Where this would be most excellent, for the PyPI case, is if the spec
could also capture [extras], which are seeing increasing usage in Python inter-package
dependencies (even though pip is bad at them).

There is no consensus in conda packaging on how to capture such "optional dependencies,"
and some packages just ship all the optional dependencies (this hurts on Big
Cloud Vendor API extras), or have multiple outputs, but again without any naming
conventions (e.g. some folk just -{extra-name}, I generally push for
-with-{extra-name}).

An extreme case might be fastapi:

# e.g. in rattler-build recipe.yaml
recipe:
  version: ${{ version }}
outputs:
  # with fully-specified purls
  - package:
      name: fastapi
      purl: pkg:pypi/fastapi@${{ version }}
    dependencies:
      run:
        - pkg:pypi/starlette >=0.40.0,<0.42.0
        - pkg:pypi/pydantic >=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0
        - pkg:pypi/typing-extensions >=4.8.0
  # or maybe it makes sense to CURIE them, using a `pip:`-like syntax
  - package:
      name: fastapi-standard
      purl:
        pkg:pypi:
          - fastapi[standard]@${{ version }}
    dependencies:
      run:
        - ${{ pin_subpackage("fastapi", exact=True) }}
        - pkg:pypi:
          - fastapi-cli[standard] >=0.0.5
          - httpx >=0.23.0
          - jinja2 >=2.11.2
          - python-multipart >=0.0.7
          - itsdangerous >=1.1.0
          - pyyaml >=5.3.1
          - ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0
          - orjson >=3.2.1
          - email-validator >=2.0.0
          - uvicorn[standard] >=0.12.0
          - pydantic-settings >=2.0.0
          - pydantic-extra-types >=2.0.0

The latter form would all but remove any package-naming impedance, making tools
like grayskull have to do much less work to maintain package resolution stuff.

@jaimergp
Copy link
Contributor

Opened #114 to keep track of the alternatives suggested in the latest comments!


## Specification

We propose to add the optional `purls: [string]` field to `PackageRecord`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @jaimergp mentioned, we should also mention in this CEP where in the built conda package this should live and where it is specifiable in the recipe.
IMO, index.json is a good fit for it as IMO, it should also go into repodata (at least into sharded).

Adding it to index.json will lead to conda-index automatically adding it to repodata.json. This is IMO a bad default in conda-index; it should instead just keep a whitelist of things to put into repodata.json instead of putting everything from index.json in there.

@pavelzw
Copy link
Member
pavelzw commented Jul 4, 2025

i included some comments, especially about where PURLs are stored in a recipe, where they are stored in the repodata, how to patch it as well as what a PURL of a conda package itself looks like.

PTAL again

Comment on lines +116 to +125
Tools that generate packages like rattler-build and conda-build should also be able to inject a PURL at build time via CLI flags.
For this, v1-recipes can use the jinja syntax from the recipes.

```bash
rattler-build build -r recipe/ \
--append-purl-pattern \
'pkg:conda/conda-forge/${{ PACKAGE_NAME }}@${{ PACKAGE_VERSION }}?build=${{ BUILD_NUMBER }}'
```

Variables that are available in the build process (like `PACKAGE_NAME`, `PACKAGE_VERSION` and `BUILD_NUMBER`) can be specified here.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this one yet. We need some way of adding the PURL to conda-forge packages.

An alternative would be to enforce with conda-smithy that something like this is always in the recipe:

about:
  purls:
    - pkg:pypi/pinject@0.14.1
    - pkg:conda/conda-forge/${{ name }}@${{ version }}?build=${{ build }}

this would be a bit more explicit. But we would not be easily able to add things like &platform=osx-arm64

Passing it through the infrastructure via CLI flags makes sure it's correct and would immediately apply to each new build though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants
0