Support Hass.io wheels / docker env (#24175)

* Support Hass.io wheels / docker env

* address comments

* fix lint
pull/24185/head
Pascal Vizeli 2019-05-30 00:30:09 +02:00 committed by Paulus Schoutsen
parent 6aeccf0330
commit d9852bc75d
4 changed files with 92 additions and 4 deletions

View File

@ -47,6 +47,9 @@ def pip_kwargs(config_dir: Optional[str]) -> Dict[str, Any]:
kwargs = { kwargs = {
'constraints': os.path.join(os.path.dirname(__file__), CONSTRAINT_FILE) 'constraints': os.path.join(os.path.dirname(__file__), CONSTRAINT_FILE)
} }
if not (config_dir is None or pkg_util.is_virtual_env()): if 'WHEELS_LINKS' in os.environ:
kwargs['find_links'] = os.environ['WHEELS_LINKS']
if not (config_dir is None or pkg_util.is_virtual_env()) and \
not pkg_util.is_docker_env():
kwargs['target'] = os.path.join(config_dir, 'deps') kwargs['target'] = os.path.join(config_dir, 'deps')
return kwargs return kwargs

View File

@ -6,6 +6,7 @@ from subprocess import PIPE, Popen
import sys import sys
from typing import Optional from typing import Optional
from urllib.parse import urlparse from urllib.parse import urlparse
from pathlib import Path
import pkg_resources import pkg_resources
from importlib_metadata import version, PackageNotFoundError from importlib_metadata import version, PackageNotFoundError
@ -21,6 +22,11 @@ def is_virtual_env() -> bool:
hasattr(sys, 'real_prefix')) hasattr(sys, 'real_prefix'))
def is_docker_env() -> bool:
"""Return True if we run in a docker env."""
return Path("/.dockerenv").exists()
def is_installed(package: str) -> bool: def is_installed(package: str) -> bool:
"""Check if a package is installed and will be loaded when we import it. """Check if a package is installed and will be loaded when we import it.
@ -42,7 +48,8 @@ def is_installed(package: str) -> bool:
def install_package(package: str, upgrade: bool = True, def install_package(package: str, upgrade: bool = True,
target: Optional[str] = None, target: Optional[str] = None,
constraints: Optional[str] = None) -> bool: constraints: Optional[str] = None,
find_links: Optional[str] = None) -> bool:
"""Install a package on PyPi. Accepts pip compatible package strings. """Install a package on PyPi. Accepts pip compatible package strings.
Return boolean if install successful. Return boolean if install successful.
@ -55,6 +62,8 @@ def install_package(package: str, upgrade: bool = True,
args.append('--upgrade') args.append('--upgrade')
if constraints is not None: if constraints is not None:
args += ['--constraint', constraints] args += ['--constraint', constraints]
if find_links is not None:
args += ['--find-links', find_links]
if target: if target:
assert not is_virtual_env() assert not is_virtual_env()
# This only works if not running in venv # This only works if not running in venv

View File

@ -27,9 +27,10 @@ class TestRequirements:
@patch('os.path.dirname') @patch('os.path.dirname')
@patch('homeassistant.util.package.is_virtual_env', return_value=True) @patch('homeassistant.util.package.is_virtual_env', return_value=True)
@patch('homeassistant.util.package.is_docker_env', return_value=False)
@patch('homeassistant.util.package.install_package', return_value=True) @patch('homeassistant.util.package.install_package', return_value=True)
def test_requirement_installed_in_venv( def test_requirement_installed_in_venv(
self, mock_install, mock_venv, mock_dirname): self, mock_install, mock_venv, mock_denv, mock_dirname):
"""Test requirement installed in virtual environment.""" """Test requirement installed in virtual environment."""
mock_venv.return_value = True mock_venv.return_value = True
mock_dirname.return_value = 'ha_package_path' mock_dirname.return_value = 'ha_package_path'
@ -45,9 +46,10 @@ class TestRequirements:
@patch('os.path.dirname') @patch('os.path.dirname')
@patch('homeassistant.util.package.is_virtual_env', return_value=False) @patch('homeassistant.util.package.is_virtual_env', return_value=False)
@patch('homeassistant.util.package.is_docker_env', return_value=False)
@patch('homeassistant.util.package.install_package', return_value=True) @patch('homeassistant.util.package.install_package', return_value=True)
def test_requirement_installed_in_deps( def test_requirement_installed_in_deps(
self, mock_install, mock_venv, mock_dirname): self, mock_install, mock_venv, mock_denv, mock_dirname):
"""Test requirement installed in deps directory.""" """Test requirement installed in deps directory."""
mock_dirname.return_value = 'ha_package_path' mock_dirname.return_value = 'ha_package_path'
self.hass.config.skip_pip = False self.hass.config.skip_pip = False
@ -77,3 +79,60 @@ async def test_install_existing_package(hass):
hass, 'test_component', ['hello==1.0.0']) hass, 'test_component', ['hello==1.0.0'])
assert len(mock_inst.mock_calls) == 0 assert len(mock_inst.mock_calls) == 0
async def test_install_with_wheels_index(hass):
"""Test an install attempt with wheels index URL."""
hass.config.skip_pip = False
mock_integration(
hass, MockModule('comp', requirements=['hello==1.0.0']))
with patch(
'homeassistant.util.package.is_installed', return_value=False
), \
patch(
'homeassistant.util.package.is_docker_env', return_value=True
), \
patch(
'homeassistant.util.package.install_package'
) as mock_inst, \
patch.dict(
os.environ, {'WHEELS_LINKS': "https://wheels.hass.io/test"}
), \
patch(
'os.path.dirname'
) as mock_dir:
mock_dir.return_value = 'ha_package_path'
assert await setup.async_setup_component(hass, 'comp', {})
assert 'comp' in hass.config.components
print(mock_inst.call_args)
assert mock_inst.call_args == call(
'hello==1.0.0', find_links="https://wheels.hass.io/test",
constraints=os.path.join('ha_package_path', CONSTRAINT_FILE))
async def test_install_on_docker(hass):
"""Test an install attempt on an docker system env."""
hass.config.skip_pip = False
mock_integration(
hass, MockModule('comp', requirements=['hello==1.0.0']))
with patch(
'homeassistant.util.package.is_installed', return_value=False
), \
patch(
'homeassistant.util.package.is_docker_env', return_value=True
), \
patch(
'homeassistant.util.package.install_package'
) as mock_inst, \
patch(
'os.path.dirname'
) as mock_dir:
mock_dir.return_value = 'ha_package_path'
assert await setup.async_setup_component(hass, 'comp', {})
assert 'comp' in hass.config.components
print(mock_inst.call_args)
assert mock_inst.call_args == call(
'hello==1.0.0',
constraints=os.path.join('ha_package_path', CONSTRAINT_FILE))

View File

@ -167,6 +167,23 @@ def test_install_constraint(mock_sys, mock_popen, mock_env_copy, mock_venv):
assert mock_popen.return_value.communicate.call_count == 1 assert mock_popen.return_value.communicate.call_count == 1
def test_install_find_links(mock_sys, mock_popen, mock_env_copy, mock_venv):
"""Test install with find-links on not installed package."""
env = mock_env_copy()
link = 'https://wheels-repository'
assert package.install_package(
TEST_NEW_REQ, False, find_links=link)
assert mock_popen.call_count == 1
assert (
mock_popen.call_args ==
call([
mock_sys.executable, '-m', 'pip', 'install', '--quiet',
TEST_NEW_REQ, '--find-links', link
], stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env)
)
assert mock_popen.return_value.communicate.call_count == 1
@asyncio.coroutine @asyncio.coroutine
def test_async_get_user_site(mock_env_copy): def test_async_get_user_site(mock_env_copy):
"""Test async get user site directory.""" """Test async get user site directory."""