Skip to content

Error when accessing private indexes in 2.0聽#3826

Closed

Description

@dougthor42

馃悶 bug report

Affected Rule

gazelle_python_manifest

Is this a regression?

Yes, 1.9.0 was working

Description

Google Artifact Registry ("GAR") does not, to my knowledge, have a way to enable the un-scoped directory listing defined by PEP 503. Gemini says this is an intentional design decision. /shrug.

Anyway, what that means is that trying to GET the /simple (or /simple/) endpoint - added in #3657 - fails with 404.

$ curl --fail-with-body -s -L --header "Authorization: Bearer $(gcloud auth print-access-token)" https://us-west2-python.pkg.dev/GCS-PROJECT/GAR-NAME/simple

<!DOCTYPE html>
<html lang=en>
  <meta charset=utf-8>
  <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
  <title>Error 404 (Not Found)!!1</title>
  <style>
    ...
  </style>
  <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
  <p><b>404.</b> <ins>That鈥檚 an error.</ins>
  <p>  <ins>That鈥檚 all we know.</ins>

馃敩 Minimal Reproduction

  1. Make a GAR
  2. Add it as an index
  3. Try to pull from it.

馃敟 Exception or Error

$ bazel run //tools/bazel/gazelle:gazelle_python_manifest.update
INFO: Invocation ID: 948bae7b-027c-4f2b-8973-64b37b86d35b
WARNING: /usr/local/google/home/dthor/dev/pyle3/MODULE.bazel:2:7: The attribute 'compatibility_level' in module() is a no-op and will be removed in a future Bazel release. Please remove it from your MODULE.bazel file.
WARNING: For repository 'bazel_skylib', the root module requires module version bazel_skylib@1.8.2, but got bazel_skylib@1.9.0 in the resolved dependency graph. Please update the version in your MODULE.bazel or set --check_direct_dependencies=off
WARNING: For repository 'aspect_bazel_lib', the root module requires module version aspect_bazel_lib@2.19.3, but got aspect_bazel_lib@2.21.2 in the resolved dependency graph. Please update the version in your MODULE.bazel or set --check_direct_dependencies=off
WARNING: For repository 'tar.bzl', the root module requires module version tar.bzl@0.7.0, but got tar.bzl@0.10.4 in the resolved dependency graph. Please update the version in your MODULE.bazel or set --check_direct_dependencies=off
WARNING: For repository 'bazel_gazelle', the root module requires module version gazelle@0.47.0, but got gazelle@0.51.3 in the resolved dependency graph. Please update the version in your MODULE.bazel or set --check_direct_dependencies=off
WARNING: For repository 'rules_java', the root module requires module version rules_java@8.14.0, but got rules_java@8.16.1 in the resolved dependency graph. Please update the version in your MODULE.bazel or set --check_direct_dependencies=off
WARNING: For repository 'platforms', the root module requires module version platforms@1.0.0, but got platforms@1.1.0 in the resolved dependency graph. Please update the version in your MODULE.bazel or set --check_direct_dependencies=off
WARNING: Remote Cache: NOT_FOUND: Unable to download blob from any provided URI: HTTP request failed with status "404 Not Found" (retrieving cached blob failed with: rpc error: code = NotFound desc = could not get action from action cache: Action result with digest "65a8ca5ff68e3b7e62969ea3ef6a013929ace1660b288e6da6eba9d26b376010/170" not found)
WARNING: Download from https://oauth2accesstoken@us-python.pkg.dev/artifact-foundry-prod/standard-python-3p-l3/simple/ failed: class java.io.FileNotFoundException GET returned 404 Not Found
WARNING: Remote Cache: NOT_FOUND: Unable to download blob from any provided URI: HTTP request failed with status "404 Not Found" (retrieving cached blob failed with: rpc error: code = NotFound desc = could not get action from action cache: Action result with digest "2c4dcf60806de2f6d23423b41275575b249a924fbf026a3e933dc0b9052720f3/169" not found)
WARNING: Download from https://oauth2accesstoken@us-west2-python.pkg.dev/GCS-PROEJCT/GAR-NAME/simple/ failed: class java.io.FileNotFoundException GET returned 404 Not Found
ERROR: /usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/external/rules_python+/python/private/pypi/simpleapi_download.bzl:260:39: Traceback (most recent call last):
        File "/usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/external/rules_python+/python/private/pypi/extension.bzl", line 378, column 25, in _pip_impl
                mods = parse_modules(
        File "/usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/external/rules_python+/python/private/pypi/extension.bzl", line 267, column 30, in parse_modules
                builder.pip_parse(
        File "/usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/external/rules_python+/python/private/pypi/hub_builder.bzl", line 60, column 47, in lambda
                pip_parse = lambda *a, **k: _pip_parse(self, *a, **k),
        File "/usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/external/rules_python+/python/private/pypi/hub_builder.bzl", line 193, column 22, in _pip_parse
                _create_whl_repos(
        File "/usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/external/rules_python+/python/private/pypi/hub_builder.bzl", line 542, column 50, in _create_whl_repos
                requirements_by_platform = parse_requirements(
        File "/usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/external/rules_python+/python/private/pypi/parse_requirements.bzl", line 212, column 36, in parse_requirements
                index_urls = get_index_urls(
        File "/usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/external/rules_python+/python/private/pypi/hub_builder.bzl", line 381, column 144, in lambda
                self._get_index_urls[python_version] = lambda ctx, distributions, *, index_url = None, extra_index_urls = None: self._simpleapi_download_fn(
        File "/usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/external/rules_python+/python/private/pypi/simpleapi_download.bzl", line 91, column 31, in simpleapi_download
                dist_urls = _get_dist_urls(
        File "/usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/external/rules_python+/python/private/pypi/simpleapi_download.bzl", line 170, column 43, in _get_dist_urls
                results[index_url] = download.wait()
        File "/usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/external/rules_python+/python/private/pypi/simpleapi_download.bzl", line 260, column 39, in lambda
                result = download.wait(),
Error in wait: java.io.IOException: Error downloading [https://oauth2accesstoken@us-west2-python.pkg.dev/GCS-PROJECT/GAR-NAME/simple/] to /usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/modextwd/rules_python++pip/https___oauth2accesstoken@us_west2_python_pkg_dev_GCS_PROJECT_GAR_NAME_simple.html: GET returned 404 Not Found
ERROR: error evaluating module extension @@rules_python+//python/extensions:pip.bzl%pip
INFO: Elapsed time: 115.278s
INFO: 0 processes.
ERROR: Build did NOT complete successfully
ERROR: Build failed. Not running target
FAILED: 
    Fetching module extension @@rules_python+//python/extensions:pip.bzl%pip; Fetch package lists from PyPI index 114s

馃實 Your Environment

Operating System:

gLinux

Output of bazel version:

$ bazel version
Bazelisk version: v1.26.0
Build label: 8.7.0
Build target: @@//src/main/java/com/google/devtools/build/lib/bazel:BazelServer
Build time: Wed May 06 18:59:43 2026 (1778093983)
Build timestamp: 1778093983
Build timestamp as int: 1778093983

Rules_python version:

Running from a git checkout of commit e7d1378 (tag 2.0.3)

Anything else relevant?

I think you asked me to review things before 2.0 came out and I never did. Sorry! 馃槶

Activity

  1. aignas commented on Jun 16, 2026

    @aignas
    Collaborator

    2.0.3 should not have this bug, so this is really strange and the unit tests validate that behaviour. Did you clear all of the caches?

    If you have 2 indexes specified, it will still query, so may need to specify your default index in addition to doing extra index urls. Could you print here the MODULE.bazel setup and the headers of the requirements files?

  2. dougthor42 commented on Jun 17, 2026

    @dougthor42
    CollaboratorAuthor

    Just tried with the official 2.0.3 instead of a git_override (ok fine technically it was a local_path_override to my clone of rules_python... 馃槅).

    --- a/MODULE.bazel
    +++ b/MODULE.bazel
    @@ -23,7 +23,7 @@ PYTHON_VERSION_NAME = "python_" + PYTHON_VERSION.replace(".", "_")
     
     PYNEXT_VERSION_NAME = "python_" + PYNEXT_VERSION.replace(".", "_") if PYNEXT_VERSION != None else None  # @unused
     
    -RULES_PYTHON_VERSION = "1.9.0"
    +RULES_PYTHON_VERSION = "2.0.3"
     
     RULES_RUST_VERSION = "0.70.0"

    Clearing caches (bazel clean --expunge_async) had no effect.

    $ bazel clean --expunge_async; bazel run //tools/bazel/gazelle:gazelle_python_manifest.update
    ...
    Error in wait: java.io.IOException: Error downloading [https://oauth2accesstoken@us-west2-python.pkg.dev/GCS-PROJECT/GAR-NAME/simple/] to /usr/local/google/home/dthor/.cache/bazel/_bazel_dthor/0f8c52850e7230283fc2f8033149fba2/modextwd/rules_python++pip/https___oauth2accesstoken@us_west2_python_pkg_dev_qh_lab_qh_py_simple.html: GET returned 404 Not Found

    Here are the relevant bits of MODULE.bazel:

    PYTHON_VERSION = "3.13.6"
    
    RULES_PYTHON_VERSION = "2.0.3"
    
    PRIVATE_INDEX_URL = "https://us-west2-python.pkg.dev/GCS-PROJECT/GAR-NAME/simple"
    
    EXPERIMENTAL_INDEX_URL_OVERRIDES = {
        "some_package": PRIVATE_INDEX_URL,
        ...,
    }
    
    pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
    pip.parse(
        experimental_index_url = AIRLOCK_INDEX_URL,  # basically a copy of pypi
        experimental_index_url_overrides = EXPERIMENTAL_INDEX_URL_OVERRIDES,
        experimental_requirement_cycles = {
            "mdit_py_plugins": [
                "markdown_it_py",
                "mdit_py_plugins",
            ],
        },
        hub_name = "pypi",
        python_version = PYTHON_VERSION,
        requirements_lock = "//:requirements.txt",
        target_platforms = ["{os}_{arch}"],
    )

    And our requirements.txt file:

    $ head `requirements.txt`
    # This file was autogenerated by uv via the following command:
    #    uv pip compile --python-version=3.13 --output-file=requirements.txt requirements.in
    --index-url https://oauth2accesstoken@us-python.pkg.dev/REDACTED/REDACTED/simple  # AIRLOCK_INDEX_URL
    --extra-index-url https://oauth2accesstoken@us-west2-python.pkg.dev/GCP-PROJECT/GAR-NAME/simple
    
    absl-py==2.3.0 \
        --hash=sha256:9824a48b654a306168f63e0d97714665f8490b8d89ec7bf2efc24bf67cf579b3 \
        --hash=sha256:d96fda5c884f1b22178852f30ffa85766d50b99e00775ea626c23304f582fc4f
        # via
        #   dm-tree

    Note that AIRLOCK_INDEX_URL also does not support the /simple endpoint:

    $ curl --fail-with-body -s -L --header "Authorization: Bearer $(gcloud auth print-access-token)" "${AIRLOCK_INDEX_URL}"
    
    <!DOCTYPE html>
    <html lang=en>
      <meta charset=utf-8>
      <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
      <title>Error 404 (Not Found)!!1</title>
      <style>
        ...
      </style>
      <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
      <p><b>404.</b> <ins>That鈥檚 an error.</ins>
      <p>  <ins>That鈥檚 all we know.</ins>
  3. aignas commented on Jun 19, 2026

    @aignas
    Collaborator

    A few things:

    • You can drop the experimental_index_url from the pip.parse. Just the overrides is fine.
    • The code that should work fine in your case and not require the calls to the /simple/ endpoint is here

    It is hard to debug remotely, so please dig in that function and add more debugging to better understand why the if in there is not triggered.

  4. dougthor42 commented on Jun 19, 2026

    @dougthor42
    CollaboratorAuthor

    TL;DR: I'm a bit of a dumb-dumb.

    We have another pip.parse & requirements lock file that includes --extra-index-url even though it doesn't need anything from the extra index. Since there are two indexes and we don't set the overrides for pip.parse, things get down to L154. And neither of our indexes support GET .../simple.

    The fix is to remove --extra-index-url from that 2nd requirements lock file.

    I believe everything is working as intended, though I wonder if it's prudent to have a fallback if GET .../simple errors out.


    It appears that _get_dist_urls is being called twice.
    On the 2nd call, index_url_overrides is empty and len(index_urls) == 2.

    ...

    Oh shit, I completely forgot that we have a 2nd pip.parse to create a hub dedicated for twine:

    pip.parse(
        experimental_index_url = AIRLOCK_INDEX_URL,
        hub_name = "pypi_twine",
        python_version = PYTHON_VERSION,
        requirements_lock = "//tools/bazel:requirements_twine.txt",
        target_platforms = ["{os}_{arch}"],
    )
    $ head tools/bazel/requirements_twine.txt
    # This file was autogenerated by uv via the following command:
    #    uv pip compile --python-version=3.13 --output-file=tools/bazel/requirements_twine.txt tools/bazel/requirements_twine.in
    --index-url https://oauth2accesstoken@us-python.pkg.dev/REDACTED/REDACTED/simple
    --extra-index-url https://oauth2accesstoken@us-west2-python.pkg.dev/GCP-PROJECT/GAR-NAME/simple
    
    cachetools==5.2.1 \
        --hash=sha256:5991bc0e08a1319bb618d3195ca5b6bc76646a49c21d55962977197b301cc1fe \
        --hash=sha256:8462eebf3a6c15d25430a8c27c56ac61340b2ecf60c9ce57afc2b97e450e47da
        # via
        #   -c tools/bazel/../../requirements.txt
    $ cat tools/bazel/requirements_twine.in
    # Requirements needed to upload Python wheels via `twine`.
    #
    # This is used as a separate rules_python pip hub so that building and
    # uploading the rust rupyle wheel doesn't take as long.
    
    --index-url https://oauth2accesstoken@us-python.pkg.dev/REDACTED/REDACTED/simple
    --extra-index-url https://oauth2accesstoken@us-west2-python.pkg.dev/GCP-PROJECT/GAR-NAME/simple
    
    # Constrain versions to match those in requirements.txt
    -c ../../requirements.txt
    
    # go/keep-sorted start
    keyrings.google-artifactregistry-auth
    twine
    # go/keep-sorted end
    

    The fix is to remove --extra-index-url from tools/bazel/requirements_twine.(in|txt).


    Also yes we can safely remove experimental_index_url = AIRLOCK_INDEX_URL.

    We can also remove our patch for whl_library.BuildWheelFromSource! I'm not 100% sure why but I'll take the win haha. (so #2640 is fixed!)

  5. rickeylev commented on Jun 19, 2026

    @rickeylev
    Collaborator

    I was looking at the GAR docs, and it says it does support the simple API: https://docs.cloud.google.com/artifact-registry/docs/python

    These private repositories use ... the simple repository API, and work with installation tools like pip.

    I couldn't find anything specific about how to setup the simple API, so presumably it's supposed to just work? But clearly it's not in, at least, your case

  6. dougthor42 commented on Jun 20, 2026

    @dougthor42
    CollaboratorAuthor

    I opened an internal bug with the Artifact Registry team. You're cc'd on it, Richard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions