From ff84219171d7746372141b694cb88839619e0ee6 Mon Sep 17 00:00:00 2001 From: Jeff Tratner Date: Thu, 22 Mar 2018 22:02:45 -0700 Subject: [PATCH 1/4] Do not inject env when calculating hashes --- pipenv/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index d26356ca..c9cf97e5 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1316,7 +1316,7 @@ def do_init( with codecs.open(project.lockfile_location, 'r') as f: lockfile = simplejson.load(f) # Update the lockfile if it is out-of-date. - p = pipfile.load(project.pipfile_location) + p = pipfile.load(project.pipfile_location, inject_env=False) # Check that the hash of the Lockfile matches the lockfile's hash. if not lockfile['_meta'].get('hash', {}).get('sha256') == p.hash: old_hash = lockfile['_meta'].get('hash', {}).get('sha256')[-6:] @@ -1659,7 +1659,7 @@ def ensure_lockfile(keep_outdated=False): with codecs.open(project.lockfile_location, 'r') as f: lockfile = simplejson.load(f) # Update the lockfile if it is out-of-date. - p = pipfile.load(project.pipfile_location) + p = pipfile.load(project.pipfile_location, inject_env=False) # Check that the hash of the Lockfile matches the lockfile's hash. if not lockfile['_meta'].get('hash', {}).get('sha256') == p.hash: old_hash = lockfile['_meta'].get('hash', {}).get('sha256')[-6:] From 1f31f40f2bce6eef660cd790a3989e92177575f3 Mon Sep 17 00:00:00 2001 From: Jeff Tratner Date: Thu, 22 Mar 2018 22:35:42 -0700 Subject: [PATCH 2/4] clean up hash change calc Add test case for pipenv hash changing --- pipenv/core.py | 25 ++++++------------------- pipenv/project.py | 17 +++++++++++++++++ tests/test_pipenv.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index c9cf97e5..78879f36 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import contextlib -import codecs import logging import os import sys @@ -1312,15 +1311,9 @@ def do_init( ) # Write out the lockfile if it doesn't exist, but not if the Pipfile is being ignored if (project.lockfile_exists and not ignore_pipfile) and not skip_lock: - # Open the lockfile. - with codecs.open(project.lockfile_location, 'r') as f: - lockfile = simplejson.load(f) - # Update the lockfile if it is out-of-date. - p = pipfile.load(project.pipfile_location, inject_env=False) - # Check that the hash of the Lockfile matches the lockfile's hash. - if not lockfile['_meta'].get('hash', {}).get('sha256') == p.hash: - old_hash = lockfile['_meta'].get('hash', {}).get('sha256')[-6:] - new_hash = p.hash[-6:] + changed_hash = project.pipfile_hash_changed() + if changed_hash: + old_hash, new_hash = changed_hash if deploy: click.echo( crayons.red( @@ -1655,15 +1648,9 @@ def ensure_lockfile(keep_outdated=False): keep_outdated = project.settings.get('keep_outdated') # Write out the lockfile if it doesn't exist, but not if the Pipfile is being ignored if project.lockfile_exists: - # Open the lockfile. - with codecs.open(project.lockfile_location, 'r') as f: - lockfile = simplejson.load(f) - # Update the lockfile if it is out-of-date. - p = pipfile.load(project.pipfile_location, inject_env=False) - # Check that the hash of the Lockfile matches the lockfile's hash. - if not lockfile['_meta'].get('hash', {}).get('sha256') == p.hash: - old_hash = lockfile['_meta'].get('hash', {}).get('sha256')[-6:] - new_hash = p.hash[-6:] + changed_hash = project.pipfile_hash_changed() + if changed_hash: + old_hash, new_hash = changed_hash click.echo( crayons.red( u'Pipfile.lock ({0}) out of date, updating to ({1})…'.format( diff --git a/pipenv/project.py b/pipenv/project.py index 9e5aeda4..b856e2b9 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import codecs import json import os import re @@ -640,3 +641,19 @@ class Project(object): def recase_pipfile(self): self.write_toml(recase_file(self._pipfile)) + + def pipfile_hash_changed(self): + """Check if hashes differ between lockfile and Pipfile. + + Returns: (old_hash, new_hash) if hash has changed + """ + # Open the lockfile. + with codecs.open(self.lockfile_location, 'r') as f: + lockfile = json.load(f) + # Update the lockfile if it is out-of-date. + p = pipfile.load(self.pipfile_location, inject_env=False) + # Check that the hash of the Lockfile matches the lockfile's hash. + if not lockfile['_meta'].get('hash', {}).get('sha256') == p.hash: + old_hash = lockfile['_meta'].get('hash', {}).get('sha256')[-6:] + new_hash = p.hash[-6:] + return old_hash, new_hash diff --git a/tests/test_pipenv.py b/tests/test_pipenv.py index 3a80e30e..c011fd7d 100644 --- a/tests/test_pipenv.py +++ b/tests/test_pipenv.py @@ -12,6 +12,7 @@ from pipenv.utils import ( ) from pipenv.vendor import toml from pipenv.vendor import delegator +from pipenv.patched import pipfile from pipenv.project import Project from pipenv.vendor.six import PY2 if PY2: @@ -1118,3 +1119,33 @@ requests = "==2.14.0" with PipenvInstance(pypi=pypi) as p: c = p.pipenv('clean') assert c.return_code == 0 + + + @pytest.mark.install + def test_environment_variable_value_does_not_change_hash(self, pypi, monkeypatch): + with PipenvInstance(chdir=True, pypi=pypi) as p: + with open(p.pipfile_path, 'w') as f: + f.write(""" +[[source]] +url = 'https://${PYPI_USERNAME}:${PYPI_PASSWORD}@pypi.python.org/simple' +verify_ssl = true +name = 'pypi' + +[requires] +python_version = '2.7' + +[packages] +flask = "==0.12.2" +""") + monkeypatch.setitem(os.environ, 'PYPI_USERNAME', 'whatever') + monkeypatch.setitem(os.environ, 'PYPI_PASSWORD', 'pass') + c = p.pipenv('install') + # sanity check on pytest + assert 'PYPI_USERNAME' not in str(pipfile.load(p.pipfile_path)) + assert c.return_code == 0 + assert not Project().pipfile_hash_changed() + monkeypatch.setitem(os.environ, 'PYPI_PASSWORD', 'pass2') + assert not Project().pipfile_hash_changed() + with open(p.pipfile_path, 'a') as f: + f.write('requests = "==2.14.0"\n') + assert Project().pipfile_hash_changed() From 8f79f13ea433d144aa2cd9fd373864d9c5936a3d Mon Sep 17 00:00:00 2001 From: Jeff Tratner Date: Fri, 23 Mar 2018 00:08:57 -0700 Subject: [PATCH 3/4] Split hash function into two --- pipenv/core.py | 18 +++++++++--------- pipenv/project.py | 17 +++++++---------- tests/test_pipenv.py | 10 +++++++--- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/pipenv/core.py b/pipenv/core.py index 78879f36..3e7c570d 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1311,14 +1311,14 @@ def do_init( ) # Write out the lockfile if it doesn't exist, but not if the Pipfile is being ignored if (project.lockfile_exists and not ignore_pipfile) and not skip_lock: - changed_hash = project.pipfile_hash_changed() - if changed_hash: - old_hash, new_hash = changed_hash + old_hash = project.get_lockfile_hash() + new_hash = project.calculate_pipfile_hash() + if new_hash != old_hash: if deploy: click.echo( crayons.red( 'Your Pipfile.lock ({0}) is out of date. Expected: ({1}).'.format( - old_hash, new_hash + old_hash[-6:], new_hash[-6:] ) ) ) @@ -1331,7 +1331,7 @@ def do_init( click.echo( crayons.red( u'Pipfile.lock ({0}) out of date, updating to ({1})…'.format( - old_hash, new_hash + old_hash[-6:], new_hash[-6:] ), bold=True, ), @@ -1648,13 +1648,13 @@ def ensure_lockfile(keep_outdated=False): keep_outdated = project.settings.get('keep_outdated') # Write out the lockfile if it doesn't exist, but not if the Pipfile is being ignored if project.lockfile_exists: - changed_hash = project.pipfile_hash_changed() - if changed_hash: - old_hash, new_hash = changed_hash + old_hash = project.get_lockfile_hash() + new_hash = project.calculate_pipfile_hash() + if new_hash != old_hash: click.echo( crayons.red( u'Pipfile.lock ({0}) out of date, updating to ({1})…'.format( - old_hash, new_hash + old_hash[-6:], new_hash[-6:] ), bold=True, ), diff --git a/pipenv/project.py b/pipenv/project.py index b856e2b9..747c6ac7 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -642,18 +642,15 @@ class Project(object): def recase_pipfile(self): self.write_toml(recase_file(self._pipfile)) - def pipfile_hash_changed(self): - """Check if hashes differ between lockfile and Pipfile. - - Returns: (old_hash, new_hash) if hash has changed - """ + def get_lockfile_hash(self): + if not os.path.exists(self.lockfile_location): + return # Open the lockfile. with codecs.open(self.lockfile_location, 'r') as f: lockfile = json.load(f) + return lockfile['_meta'].get('hash', {}).get('sha256') + + def calculate_pipfile_hash(self): # Update the lockfile if it is out-of-date. p = pipfile.load(self.pipfile_location, inject_env=False) - # Check that the hash of the Lockfile matches the lockfile's hash. - if not lockfile['_meta'].get('hash', {}).get('sha256') == p.hash: - old_hash = lockfile['_meta'].get('hash', {}).get('sha256')[-6:] - new_hash = p.hash[-6:] - return old_hash, new_hash + return p.hash diff --git a/tests/test_pipenv.py b/tests/test_pipenv.py index c011fd7d..db1575d1 100644 --- a/tests/test_pipenv.py +++ b/tests/test_pipenv.py @@ -1139,13 +1139,17 @@ flask = "==0.12.2" """) monkeypatch.setitem(os.environ, 'PYPI_USERNAME', 'whatever') monkeypatch.setitem(os.environ, 'PYPI_PASSWORD', 'pass') + assert Project().get_lockfile_hash() is None c = p.pipenv('install') + lock_hash = Project().get_lockfile_hash() + assert lock_hash is not None + assert lock_hash == Project().calculate_pipfile_hash() # sanity check on pytest assert 'PYPI_USERNAME' not in str(pipfile.load(p.pipfile_path)) assert c.return_code == 0 - assert not Project().pipfile_hash_changed() + assert Project().get_lockfile_hash() == Project.calculate_pipfile_hash() monkeypatch.setitem(os.environ, 'PYPI_PASSWORD', 'pass2') - assert not Project().pipfile_hash_changed() + assert Project().get_lockfile_hash() == Project.calculate_pipfile_hash() with open(p.pipfile_path, 'a') as f: f.write('requests = "==2.14.0"\n') - assert Project().pipfile_hash_changed() + assert Project().get_lockfile_hash() != Project.calculate_pipfile_hash() From 5cf7a7d5523316012c1cbd12ed27553e10a3e7c3 Mon Sep 17 00:00:00 2001 From: Jeff Tratner Date: Fri, 23 Mar 2018 00:17:28 -0700 Subject: [PATCH 4/4] Add doc note about environment variables --- docs/advanced.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/advanced.rst b/docs/advanced.rst index f1297388..be2d8905 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -38,6 +38,23 @@ If you'd like a specific package to be installed with a specific package index, Very fancy. +☤ Injecting credentials into Pipfiles via environment variables +----------------------------------------------------------------- + + +Pipenv will expand environment variables (if defined) in your Pipfile. Quite +useful if you need to authenticate to a private PyPI:: + + [[source]] + url = "https://$USERNAME:${PASSWORD}@mypypi.example.com/simple" + verify_ssl = true + name = "pypi" + +Luckily - pipenv will hash your Pipfile *before* expanding environment +variables (and, helpfully, will substitute the environment variables again when +you install from the lock file - so no need to commit any secrets! Woo!) + + ☤ Specifying Basically Anything -------------------------------