Skip to content
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

[master] Fix Python3.13 compatibility regarding urllib.parse module #66899

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

marmarek
Copy link
Contributor

What does this PR do?

Python 3.13 fixed handling relative paths in urllib.parse module. Specifically, relative file URL is now constructed as file:path instead of converting it to absolute file:///path. This breaks salt.utils.url.create which expects file:/// specifically. The mismatch results in for example changing salt://top.sls into salt://.sls and thus not finding the top file.

Fix this by handling both prefixes.

Relevant python change: python/cpython#85110

What issues does this PR fix or reference?

Fixes: #66898

Merge requirements satisfied?

[NOTICE] Bug fixes or features added to Salt require tests.

Commits signed with GPG?

Yes

Please review Salt's Contributing Guide for best practices, including the
PR Guidelines.

See GitHub's page on GPG signing for more information about signing commits with GPG.

@marmarek marmarek requested a review from a team as a code owner September 18, 2024 03:04
@salt-project-bot-prod-environment salt-project-bot-prod-environment bot changed the title Fix Python3.13 compatibility regarding urllib.parse module [master] Fix Python3.13 compatibility regarding urllib.parse module Sep 18, 2024
Copy link
Contributor

@twangboy twangboy left a comment

Choose a reason for hiding this comment

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

Could you please write a test for this

Copy link
Contributor

@dmurphy18 dmurphy18 left a comment

Choose a reason for hiding this comment

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

Needs tests written in pytest

@marmarek
Copy link
Contributor Author

Could you please write a test for this

The existing tests/unit/utils/test_url.py already covers this. And indeed, without my change, it fails already on Python 3.13rc2 (Fedora 41 beta) or Python 3.12.6 (Fedora 40). The change that broke did got backported to Python 3.12.6 (released about 2 weeks ago). I see you have Fedora 40 in CI already, but it seems to run use onedir (and Python 3.10.x inside) instead of Python from Fedora. Is there any CI job that tests on Python 3.12.x (or 3.13)?

@twangboy
Copy link
Contributor

We currently only support up to Python 3.10. There are dependency issues we need to get figured out across all the Operating systems we support before we can officially support anything newer.

@marmarek
Copy link
Contributor Author

In absence of newer Python to actually test the behavior there, do you want me to mimic its new behavior by patching urllib.parse.urlunparse then (I find it weird, but if you insist I can add it)? The code is already tested that it still works on Python 3.10. And once you will be adding support for Python 3.12, you will have one thing less to fix :)

BTW, does the CI report somewhere test coverage? From what I can see, coverage-related jobs got skipped...

algitbot pushed a commit to alpinelinux/aports that referenced this pull request Sep 19, 2024
algitbot pushed a commit to alpinelinux/aports that referenced this pull request Sep 19, 2024
Copy link

@slimlv slimlv left a comment

Choose a reason for hiding this comment

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

how many "If/return"s you going to insert ?
note that changes in python code was introduced in minor release 3.12.6

try to make smart bulletproof improvements , for example

url = salt.utils.data.decode(urlunparse(("file", "", "/" + path, "", query, "")))

@twangboy
Copy link
Contributor

I think, just a unit test to make sure that if the URL starts with file:/// it gets converted to 'salt://`

Comment on lines 47 to 53

query = f"saltenv={saltenv}" if saltenv else ""
url = salt.utils.data.decode(urlunparse(("file", "", path, "", query, "")))
return "salt://{}".format(url[len("file:///") :])
# urlunparse changed behavior in Python 3.13
if url.startswith("file:///"):
return "salt://{}".format(url[len("file:///") :])
return "salt://{}".format(url[len("file:") :])
Copy link
Contributor

@bdrx312 bdrx312 Sep 29, 2024

Choose a reason for hiding this comment

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

This create function should not be doing path = path.replace("\\", /) and probably not

if salt.utils.platform.is_windows():
path = salt.utils.path.sanitize_win_path(path)

Instead it should use urllib.request.pathname2url or some other python builtin functionality.

Especially since the sanitize_win_path function is clearly miswritten:

def sanitize_win_path(winpath):
    """
    Remove illegal path characters for windows
    """
    intab = "<>:|?*"
    if isinstance(winpath, str):
        winpath = winpath.translate({ord(c): "_" for c in intab})
    elif isinstance(winpath, str):

Notice the duplicate isinstance(winpath, str) in the elif that will never occur.

I would prefer instead of assuming that the string starts with file: or file:/// we do a regex search replace like re.sub(r"^file:(?:///)?", "", url) Also use f string instead of format.

return f'salt://{re.sub(r"^file:(?:///)?", "", url)}'

Copy link

@slimlv slimlv Oct 1, 2024

Choose a reason for hiding this comment

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

return f'salt://{re.sub(r"^file:(?:/+)?", "", url)}'

Copy link
Contributor

@bdrx312 bdrx312 Oct 1, 2024

Choose a reason for hiding this comment

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

return f'salt://{re.sub(r"^file:(?:/+)?", "", url)}'

If you are going to do zero or more you don't + and ? nor parenthesis you just use *

return f'salt://{re.sub(r"^file:/*", "", url)}'

Copy link
Contributor

Choose a reason for hiding this comment

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

and if you want to do 0 to 3 / then it would be

return f'salt://{re.sub(r"^file:/{0,3}", "", url)}'

or

return f'salt://{re.sub(r"^file:/{,3}", "", url)}'

but I think the explicit 0 is more readable.

@marmarek
Copy link
Contributor Author

marmarek commented Oct 4, 2024

I think, just a unit test to make sure that if the URL starts with file:/// it gets converted to 'salt://`

Most tests in tests/unit/utils/test_url.py do that already, for example test_create_url. Those tests do fail when running on Python 3.13 (or Python 3.12.6), so this regression is covered already. Your CI will notice it as soon as you start testing on such Python version.
And BTW, the file:// prefix is added only in salt.utils.url.create(), just to be replaced with salt:// - it doesn't exist in the function call argument.

Python 3.13 fixed handling relative paths in urllib.parse module.
Specifically, relative file URL is now constructed as file:path instead
of converting it to absolute file:///path. This breaks
salt.utils.url.create which expects file:/// specifically. The mismatch
results in for example changing salt://top.sls into salt://.sls and thus
not finding the top file.

Fix this by handling both prefixes.

Relevant python change: python/cpython#85110
Fixes: saltstack#66898
@dead10ck
Copy link
Contributor

I'd like to remind the maintainers that this bug completely and utterly breaks salt on many systems, especially because this "fix" has been backported to Python 3.12.

I understand that 3.10 is currently the officially supported version, but the reality is that a lot of folks are not, and never will, use the onedir package, so this is high impact regardless of official compatibility guarantees.

I understand the desire for tests with changes in the general case, but I think folks have to keep in mind that that should be a rule of thumb, not a hard requirement in every case no matter what the cost. Sometimes fixing high impact bugs, level of effort to benefit ratio, and potential risk for introducing new regressions in more strongly supported environments should weigh into the decision to block merging unless there are tests. It's been established that there is significant preexisting coverage on this code path, so blocking on adding more tests is not doing anyone any good.

@hartwork
Copy link

hartwork commented Dec 18, 2024

FYI Fedora has integrated this patch into their Salt packaging by now at https://src.fedoraproject.org/rpms/salt/blob/main/f/urllib.patch 👏

Copy link
Contributor

@twangboy twangboy left a comment

Choose a reason for hiding this comment

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

Is there a way to write a test for this?

@marmarek
Copy link
Contributor Author

Is there a way to write a test for this?

See #66899 (comment)
tl;dr: existing tests cover this extensively already. But CI doesn't run Python 3.13 tests (yet?).

@twangboy
Copy link
Contributor

Ah, true

@twangboy twangboy added this to the Argon v3008.0 milestone Feb 11, 2025
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.

[TECH DEBT] Salt fails to load top.sls on Python3.13rc2
9 participants