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

Add basic publish command #86

Merged
merged 37 commits into from
May 12, 2023
Merged

Add basic publish command #86

merged 37 commits into from
May 12, 2023

Conversation

cnpryer
Copy link
Contributor

@cnpryer cnpryer commented May 1, 2023

Closes #85

How it works:

chrispryer@Chriss-MacBook-Pro ~/g/s/rye-publish> rye publish --repository-url https://test.pypi.org/legacy/
Enter a passphrase (optional):
  1. Require access token
  2. Check args (--token)
  3. Check ~/.rye/credentials
  4. Prompt user ("go to https://upload.pypi.org/legacy/")
  5. Offer encryption/decryption via passphrase
  6. Store in ~/.rye/credentials encoded keyed by --repository (defaults "pypi")
  7. Publish targets via twine (--repository-url)

See #86 (comment)

Summary of changes:

  • Added publish command
    • Added twine to bootstrapping
    • Added age for encrypt/decrypt with passphrases
    • Added hex for encode/decode token data
    • Added dialoguer for passphrase prompt

TODO:

  • Add command with basic requirements from twine
  • Allow pattern and multiple targets for dist positional arguments
  • Basic token handling
  • Hidden input
  • Encode/decode
  • Successful https://test.pypi.org/ publish
  • Clean up
  • Validated encryption & decryption via passphrase and ~/.rye/credentials

Considerations

  • Supporting pypirc in the first-pass
  • ring is smaller but age is easier to reason about being new to this and keyring looks nice (see comment tagged below)
  • Should rye bootstrap with an empty ~/.rye/credentials?
  • Adding a --no-passphrase / --no-interaction flag
  • OIDC support
  • Using rye with env vars

See #86 (comment)

@cnpryer cnpryer marked this pull request as ready for review May 1, 2023 21:28
rye/src/cli/publish.rs Outdated Show resolved Hide resolved
Co-authored-by: Chris Pryer <14341145+cnpryer@users.noreply.github.com>
@mitsuhiko
Copy link
Collaborator

I think it's reasonable to use twine behind the scenes but I wonder if it wouldn't be better to explicitly handle authentication. The way twine currently works makes it quite user unfriendly to get to the right experience (eg: upload with tokens).

@cnpryer cnpryer marked this pull request as draft May 2, 2023 11:23
@cnpryer
Copy link
Contributor Author

cnpryer commented May 2, 2023

Love it. How does this sound?

  1. rye publish
  2. "No access token found for <url>\nAccess token: <wait for user input>"

IIRC that's roughly how cargo handles it.

@mitsuhiko
Copy link
Collaborator

I think what makes most sense is to propose something like this:

  • rye publish if it does not have a token on file, prints a message for the user to go to https://pypi.org/manage/account/token/ and create a global token
  • It offers to encrypt the token with a passphrase
  • It stores the token in ~/.rye/credentails keyed by repository
  • Whenever it needs a token, it feeds that token directly to twine, optionally decrypting it with the provided or prompted passphrase

Since it's not possible to restrict a token to a not yet created project, we probably want to create global tokens all the way but it would be possible to only ever store a restricted token by using pypitoken.

@cnpryer
Copy link
Contributor Author

cnpryer commented May 2, 2023

Sounds like a plan. Just a heads up I'll be pretty busy until Thursday. If I can't pick this back up before then I'll tackle this into the weekend.

@cnpryer cnpryer force-pushed the publish branch 2 times, most recently from c7a1549 to 46379b0 Compare May 3, 2023 04:06
@cnpryer
Copy link
Contributor Author

cnpryer commented May 3, 2023

@cnpryer
Copy link
Contributor Author

cnpryer commented May 9, 2023

I'll mark this as ready to review. Struggling to find time, so maybe I can get some feedback for the next window I have to work on this. It might make sense to consider what maturin does. I've added a broader "considerations" list to the issue's description.

Edit: I’ll resolve the conflicts and probably push a few more changes to make this a little more robust. I can do this later, then I’ll mark as ready for review.

@mitsuhiko
Copy link
Collaborator

I will review this today!

@cnpryer
Copy link
Contributor Author

cnpryer commented May 9, 2023

I will review this today!

Thanks! I want to put a little more time into to the url cli argument (eg: https://test.pypi.org/legacy will fail with a redirect due to the missing trailing /) and a couple other house cleaning items, but otherwise it should be structurally ready for some feedback.

@cnpryer cnpryer marked this pull request as ready for review May 9, 2023 18:30
let token = if let Some(token) = cmd.token {
let secret = Secret::new(token);
let maybe_encrypted = prompt_maybe_encrypt(&secret)?;
let encoded = hex::encode(maybe_encrypted.expose_secret());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could just encode the encrypted bytes. If its not encrypted we could just store it as-is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added in abd82b2

Copy link
Contributor Author

@cnpryer cnpryer left a comment

Choose a reason for hiding this comment

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

I'm thinking I could provide some of my thoughts to give you a kind of warm-start to help with this review.

  • I think the Url change is sensible enough for me to go and make. I'll try it out and if it works I'll push. (done: ba19eff)

  • I don't like always encoding/decoding the token. It's an easy fix and probably sensible to make, so I'll add that to my list. (done: abd82b2)

  • I don't like pad_hex+escape_string use, so I might add this to my list. At least to better understand the serialization/deserialization of the data.

  • You apparently own dialoguer which maturin uses. I'm thinking about adding this to my list and swapping rpassword out for it (used for passphrase prompt; done: b86d858).

Otherwise here are some questions we can focus on:

  • Should we bootstrap with a ~/.rye/credentials file? Currently this PR creates it if it doesn't exist upon request.
  • Should we add a "package-repository" table to the credentials file? Right now it's just
[pypi]
token = "your token"

So maybe something like

[package-repository]
pypi = {token = "your token"}
  • Should we add support for pypirc? And if so, do we want to always prioritize this file over any credentials data? We can just pass this immediately to twine if it's provided.
  • Do we want to support OIDC right away or get this first-pass merged and follow up?
  • Could we add some kind of interaction-skipping argument (something like --no-passphrase)?
  • Should we add environment variable support for the token? If so, RYE_REPOSITORY_URL_TOKEN or something?

Comment on lines 28 to 29
#[arg(long, default_value = "https://upload.pypi.org/legacy/")]
repository_url: String,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
#[arg(long, default_value = "https://upload.pypi.org/legacy/")]
repository_url: String,
#[arg(long, default_value = "https://upload.pypi.org/legacy/")]
repository_url: Url,

This could probably work. I'll check this out tonight.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seems to work.

I made a comment about handling trailing slashes in the url. If twine doesn't handle this upfront for the user maybe we don't either. Here's the output the user would see

Enter a passphrase (optional): 
Uploading distributions to https://test.pypi.org/legacy
Uploading rye_publish-0.1.0-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.8/3.8 kB • 00:00 • ?
ERROR    RedirectDetected: https://test.pypi.org/legacy attempted to redirect to                    
         https://test.pypi.org/legacy/.                                                             
         Your repository URL is missing a trailing slash. Please add it and try again.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added Url usage in ba19eff

.get(repository)
.and_then(|table| table.get("token"))
.map(|token| token.to_string())
.map(clean_hex)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

clean_hex is hacky, so if you want me to spend more time here I'm happy to. This should also move if we do https://github.com/mitsuhiko/rye/pull/86/files#r1189156856 (conditional encode/decode)

Copy link
Contributor Author

@cnpryer cnpryer May 10, 2023

Choose a reason for hiding this comment

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

Split into pad_hex and escape_string but I'd still want to better understand this. I think I understand the hex padding, but having to escape the string is confusing me.

.map(|token| token.to_string())
.map(clean_hex)
{
let decoded = hex::decode(token)?;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added in abd82b2

let secret = Secret::new(token);
let maybe_encrypted = prompt_maybe_encrypt(&secret)?;
credentials[repository]["token"] =
Item::Value(hex::encode(maybe_encrypted.expose_secret()).into());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added in abd82b2

@cnpryer
Copy link
Contributor Author

cnpryer commented May 12, 2023

I have time today and tomorrow morning to work on any changes here if needed.

@@ -46,6 +46,7 @@ platformdirs==3.4.0
pyproject_hooks==1.0.0
requests==2.29.0
tomli==2.0.1
twine==4.0.2
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since this adds a new dependency the SELF_VERSION needs bumping.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh nice

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added in e828154

secret
};

let mut publish_cmd = Command::new(venv.join("bin/python"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should use the new utility function that makes this work on windows (get_venv_python_bin).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added in f6dc079

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oof I can fix the commit message if you'd like

rye/src/cli/publish.rs Outdated Show resolved Hide resolved
@mitsuhiko
Copy link
Collaborator

Looks good!

@mitsuhiko mitsuhiko merged commit 10b0875 into astral-sh:main May 12, 2023
6 checks passed
@cnpryer cnpryer deleted the publish branch May 12, 2023 22:21
ischaojie pushed a commit to ischaojie/rye that referenced this pull request May 13, 2023
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.

Publish command
3 participants