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

Fix issue #3901: environment variables parse in commands #4192

Closed
wants to merge 25 commits into from

Conversation

Hammond95
Copy link

@Hammond95 Hammond95 commented Apr 14, 2020

The issue

This fixes #3901

The fix

My code will fix the specific use case reported in the issue, however this code still requires some improvements in my opinion. Before proceeding with these improvement, I think that we should define which use cases this feature should cover.

Some examples:

  • do we want to solve variables into the command? ${MYVAR} or $MY_VAR
  • do we want to allow the execution of multiple commands if separated by semicolon? echo "hello"; python --version
  • do we want to allow multiple command execution with | or && or || operators?

Some possible improvements

  • Also add a specific class to manage windows env variables
  • manage reserved keywords
  • dynamically instantiate the correct class based on the running OS

The checklist

  • Associated issue
  • A news fragment in the news/ directory to describe this fix with the extension .bugfix, .feature, .behavior, .doc. .vendor. or .trivial (this will appear in the release changelog). Use semantic line breaks and name the file after the issue number or the PR #.

@Hammond95 Hammond95 marked this pull request as draft April 14, 2020 16:14
@Hammond95

This comment has been minimized.

Hammond95 and others added 2 commits April 14, 2020 20:17
…GEX in case of unquoted variable assignment.
Add exclusion of some characters (&, |, <,>) in the regex ENV_VAR_VALUE_RE…
@chysi

This comment has been minimized.

@Hammond95

This comment has been minimized.

@Hammond95 Hammond95 changed the title Fix for issue #3901 Fix issue #3901: pipenv run environment variable parse in commands Apr 15, 2020
@Hammond95 Hammond95 changed the title Fix issue #3901: pipenv run environment variable parse in commands Fix issue #3901: environment variables parse in commands Apr 15, 2020
@Hammond95 Hammond95 marked this pull request as ready for review April 16, 2020 09:15
@techalchemy techalchemy added Category: CLI Issue relates to the CLI Category: Security Relates to security Priority: Low This item is low priority and may not be looked at in the next few release cycles. Status: Awaiting Review This item is currently awaiting review. Type: Discussion This issue is open for discussion. Type: Enhancement 💡 This is a feature or enhancement request. Type: Possible Bug This issue describes a possible bug in pipenv. labels Apr 23, 2020
@uranusjr
Copy link
Member

uranusjr commented Apr 24, 2020

I’m inclined to use structured input instead:

serve = {
    cmd = "uvicorn mysite:app --reload",
    env = { DEBUG = true },
}

This gets rid of all the parsing and platform-specific behaviours.

Multiple commands can be supported by supplying an array to cmd (or we can introduce a new cmds key instead). A flag can be added to specify how the chain continues after an error occurs (i.e. switch between ;, ||, and &&).

I don’t think it’s a good idea to re-invent Bash command parsing. Just let the user pass in something structured, it’s easier for everyone. (And by everyone I mean including the user. Most people in the world likely don’t use shell syntaxes as a native mental model, so requireing the input to be shell syntax means they also need to “serialise” their thought.)

@Hammond95
Copy link
Author

@uranusjr This makes sense, I felt like I was re-inventing the wheel.
Should we also provide retro-compatibility for the commands defined as:
command = 'foo --bar=goo' ?

@uranusjr
Copy link
Member

@Hammond95 Definitely. It should continue to exist and work identically to command = { cmd = 'foo --bar=goo' }

@uranusjr
Copy link
Member

Oh, I just spotted a mistake in my structured example. Environment variable values must be strings (of course):

serve = {
    cmd = "uvicorn mysite:app --reload",
    env = { DEBUG = 'true' },
}

This brings up an interesting point. We’ll either need to do some introspection before passing the env values to subprocess (IIRC it would crash if any value is not string), or str(value) on the way in. But we’ll need to handle certain values carefully if we go with the str() route: users might not expect true to become True when they receive it in the subprocess.

@Hammond95
Copy link
Author

@uranusjr Yeah, that's a good point, I will give a look at this.

Another question:
If we run pipenv run --help we get

Usage: pipenv run [OPTIONS] COMMAND [ARGS]...

  Spawns a command installed into the virtualenv.

Options:
  --python TEXT       Specify which version of Python virtualenv should use.
  --three / --two     Use Python 3/2 when creating virtualenv.
  --clear             Clears caches (pipenv, pip, and pip-tools).  [env var:
                      PIPENV_CLEAR]

  -v, --verbose       Verbose mode.
  --pypi-mirror TEXT  Specify a PyPI mirror.
  -h, --help          Show this message and exit.

Does it make sense to have the options --python, --three/--two, --clear, --pypi-mirror associated with this command?

@techalchemy
Copy link
Member

Does it make sense to have the options --python, --three/--two, --clear, --pypi-mirror associated with this command?

I had a separate discussion recently with @ncoghlan about this question. The answer is 'probably not', but most likely we should address that in a separate PR since we have a lot of extra flags and arguments being passed around

@Hammond95
Copy link
Author

Not sure if this requires a dedicated issue, but I have also found a possible problem in the file project.py:

In the method:

    def build_script(self, name, extra_args=None):
        try:
            script = Script.parse(self.parsed_pipfile["scripts"][name])
        except KeyError:
            script = Script(name)
        if extra_args:
            script.extend(extra_args)
        return script

in case you provide a non-existing key, pipenv will try to treat the string as a command, so the following will work as expected:

pipenv run python --version

this won't:

pipenv run "python --version"

@techalchemy
Copy link
Member

in case you provide a non-existing key, pipenv will try to treat the string as a command, so the following will work as expected:

pipenv run python --version

Yes, This is the desired behavior

this won't:

pipenv run "python --version"

This seems like a bug and it also seems kind of silly that we wouldn't be calling Script.parse() here... based on the commit history it doesn't seem like there is any reason beyond the fact that we also accept extra_args. I went ahead and found this instance in core.py where we are using the code that way (and we also test that functionality):

    try:
        script = project.build_script(command, args)
        cmd_string = ' '.join([script.command] + script.args)
        if environments.is_verbose():
            click.echo(crayons.normal("$ {0}".format(cmd_string)), err=True)
    except ScriptEmptyError:
        click.echo("Can't run script {0!r}-it's empty?", err=True)
    run_args = [script]

I vaguely recall having a conversation with @uranusjr about this and whether there is any reason not to just parse the command all the time. @uranusjr -- can you think of any reason to tackle this a bit differently? As I recall there was a reason...

@uranusjr
Copy link
Member

I’m confused. Do you mean pipenv run "python --version" should call python with --version as an argument, or call an executable named "python --version"? I would definitely expect the latter, and the build_script implementation mentioned above looks correct to me.

@Hammond95
Copy link
Author

@uranusjr Well maybe you're right and the command provided in quotes shouldn't be run as python with --version argument.

But probably we should avoid inline command execution at all 🤔 and keep that only as a "caller" of the keys defined in the [scripts] section, let me know what you think.

Also in this case the command (pipenv run hello=world python --version) related to the issue, will fail...

@uranusjr
Copy link
Member

But probably we should avoid inline command execution at all 🤔 and keep that only as a "caller" of the keys defined in the [scripts] section, let me know what you think.

Honestly, I agree (maybe except I’d keep python). But it’s way too late for this, unfortunately. There are simply too many people relying on this feature, removing it is not an option.

@Hammond95 Hammond95 marked this pull request as draft September 23, 2021 12:47
@Hammond95 Hammond95 closed this Sep 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Category: CLI Issue relates to the CLI Category: Security Relates to security Priority: Low This item is low priority and may not be looked at in the next few release cycles. Status: Awaiting Review This item is currently awaiting review. Type: Discussion This issue is open for discussion. Type: Enhancement 💡 This is a feature or enhancement request. Type: Possible Bug This issue describes a possible bug in pipenv.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

environment variables in pipfile scripts are not recognized correctly
4 participants