Skip to content

Commit 7808751

Browse files
committed
Kfp support for pip trusted host
Signed-off-by: Diego Lovison <diegolovison@gmail.com>
1 parent 0d098db commit 7808751

File tree

5 files changed

+132
-17
lines changed

5 files changed

+132
-17
lines changed

sdk/python/kfp/cli/component.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ def __init__(
155155
self._base_image = None
156156
self._target_image = None
157157
self._pip_index_urls = None
158+
self._pip_trusted_hosts = None
158159
self._load_components()
159160

160161
def _load_components(self):
@@ -214,11 +215,16 @@ def _load_components(self):
214215
logging.info(f'Using target image: {self._target_image}')
215216

216217
pip_index_urls = []
218+
pip_trusted_hosts = []
217219
for comp in self._components:
218220
if comp.pip_index_urls is not None:
219221
pip_index_urls.extend(comp.pip_index_urls)
222+
if comp.pip_trusted_hosts is not None:
223+
pip_trusted_hosts.extend(comp.pip_trusted_hosts)
220224
if pip_index_urls:
221225
self._pip_index_urls = list(dict.fromkeys(pip_index_urls))
226+
if pip_trusted_hosts:
227+
self._pip_trusted_hosts = list(dict.fromkeys(pip_trusted_hosts))
222228

223229
def _maybe_write_file(self,
224230
filename: str,
@@ -277,7 +283,7 @@ def generate_kfp_config(self):
277283

278284
def maybe_generate_dockerfile(self, overwrite_dockerfile: bool = False):
279285
index_urls_options = component_factory.make_index_url_options(
280-
self._pip_index_urls)
286+
self._pip_index_urls, self._pip_trusted_hosts)
281287
dockerfile_contents = _DOCKERFILE_TEMPLATE.format(
282288
base_image=self._base_image,
283289
maybe_copy_kfp_package=self._maybe_copy_kfp_package,

sdk/python/kfp/cli/component_test.py

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def _make_component(
3737
packages_to_install: Optional[List[str]] = None,
3838
output_component_file: Optional[str] = None,
3939
pip_index_urls: Optional[List[str]] = None,
40+
pip_trusted_hosts: Optional[List[str]] = None,
4041
) -> str:
4142
return textwrap.dedent('''
4243
from kfp.dsl import *
@@ -46,7 +47,8 @@ def _make_component(
4647
target_image={target_image},
4748
packages_to_install={packages_to_install},
4849
output_component_file={output_component_file},
49-
pip_index_urls={pip_index_urls})
50+
pip_index_urls={pip_index_urls},
51+
pip_trusted_hosts={pip_trusted_hosts})
5052
def {func_name}():
5153
pass
5254
''').format(
@@ -55,7 +57,8 @@ def {func_name}():
5557
packages_to_install=repr(packages_to_install),
5658
output_component_file=repr(output_component_file),
5759
pip_index_urls=repr(pip_index_urls),
58-
func_name=func_name)
60+
func_name=func_name,
61+
pip_trusted_hosts=repr(pip_trusted_hosts))
5962

6063

6164
def _write_file(filename: str, file_contents: str):
@@ -527,9 +530,9 @@ def test_docker_file_is_created_correctly_with_two_urls(self):
527530
528531
WORKDIR /usr/local/src/kfp/components
529532
COPY runtime-requirements.txt runtime-requirements.txt
530-
RUN pip install --index-url https://pypi.org/simple --trusted-host https://pypi.org/simple --extra-index-url https://example.com/pypi/simple --trusted-host https://example.com/pypi/simple --no-cache-dir -r runtime-requirements.txt
533+
RUN pip install --index-url https://pypi.org/simple --extra-index-url https://example.com/pypi/simple --trusted-host https://pypi.org/simple --trusted-host https://example.com/pypi/simple --no-cache-dir -r runtime-requirements.txt
531534
532-
RUN pip install --index-url https://pypi.org/simple --trusted-host https://pypi.org/simple --extra-index-url https://example.com/pypi/simple --trusted-host https://example.com/pypi/simple --no-cache-dir kfp==1.2.3
535+
RUN pip install --index-url https://pypi.org/simple --extra-index-url https://example.com/pypi/simple --trusted-host https://pypi.org/simple --trusted-host https://example.com/pypi/simple --no-cache-dir kfp==1.2.3
533536
COPY . .
534537
'''))
535538

@@ -614,6 +617,66 @@ def test_dockerfile_can_contain_custom_kfp_package(self):
614617
self.assertTrue(contents.startswith(file_start))
615618
self.assertRegex(contents, 'RUN pip install --no-cache-dir kfp-*')
616619

620+
@mock.patch('kfp.__version__', '1.2.3')
621+
def test_docker_file_is_created_one_trusted_host(self):
622+
component = _make_component(
623+
func_name='train',
624+
target_image='custom-image',
625+
pip_index_urls=['https://pypi.org/simple'],
626+
pip_trusted_hosts=['pypi.org'])
627+
_write_components('components.py', component)
628+
629+
result = self.runner.invoke(
630+
self.cli,
631+
['build', str(self._working_dir)],
632+
)
633+
self.assertEqual(result.exit_code, 0)
634+
self._docker_client.api.build.assert_called_once()
635+
self.assert_file_exists_and_contains(
636+
'Dockerfile',
637+
textwrap.dedent('''\
638+
# Generated by KFP.
639+
640+
FROM python:3.8
641+
642+
WORKDIR /usr/local/src/kfp/components
643+
COPY runtime-requirements.txt runtime-requirements.txt
644+
RUN pip install --index-url https://pypi.org/simple --trusted-host pypi.org --no-cache-dir -r runtime-requirements.txt
645+
646+
RUN pip install --index-url https://pypi.org/simple --trusted-host pypi.org --no-cache-dir kfp==1.2.3
647+
COPY . .
648+
'''))
649+
650+
@mock.patch('kfp.__version__', '1.2.3')
651+
def test_docker_file_is_created_two_trusted_host(self):
652+
component = _make_component(
653+
func_name='train',
654+
target_image='custom-image',
655+
pip_index_urls=['https://pypi.org/simple'],
656+
pip_trusted_hosts=['pypi.org', 'pypi.org:8888'])
657+
_write_components('components.py', component)
658+
659+
result = self.runner.invoke(
660+
self.cli,
661+
['build', str(self._working_dir)],
662+
)
663+
self.assertEqual(result.exit_code, 0)
664+
self._docker_client.api.build.assert_called_once()
665+
self.assert_file_exists_and_contains(
666+
'Dockerfile',
667+
textwrap.dedent('''\
668+
# Generated by KFP.
669+
670+
FROM python:3.8
671+
672+
WORKDIR /usr/local/src/kfp/components
673+
COPY runtime-requirements.txt runtime-requirements.txt
674+
RUN pip install --index-url https://pypi.org/simple --trusted-host pypi.org --trusted-host pypi.org:8888 --no-cache-dir -r runtime-requirements.txt
675+
676+
RUN pip install --index-url https://pypi.org/simple --trusted-host pypi.org --trusted-host pypi.org:8888 --no-cache-dir kfp==1.2.3
677+
COPY . .
678+
'''))
679+
617680

618681
if __name__ == '__main__':
619682
unittest.main()

sdk/python/kfp/dsl/component_decorator.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def component(func: Optional[Callable] = None,
2727
pip_index_urls: Optional[List[str]] = None,
2828
output_component_file: Optional[str] = None,
2929
install_kfp_package: bool = True,
30-
kfp_package_path: Optional[str] = None):
30+
kfp_package_path: Optional[str] = None,
31+
pip_trusted_hosts: Optional[List[str]] = None):
3132
"""Decorator for Python-function based components.
3233
3334
A KFP component can either be a lightweight component or a containerized
@@ -114,7 +115,8 @@ def pipeline():
114115
pip_index_urls=pip_index_urls,
115116
output_component_file=output_component_file,
116117
install_kfp_package=install_kfp_package,
117-
kfp_package_path=kfp_package_path)
118+
kfp_package_path=kfp_package_path,
119+
pip_trusted_hosts=pip_trusted_hosts)
118120

119121
return component_factory.create_component_from_func(
120122
func,
@@ -124,4 +126,5 @@ def pipeline():
124126
pip_index_urls=pip_index_urls,
125127
output_component_file=output_component_file,
126128
install_kfp_package=install_kfp_package,
127-
kfp_package_path=kfp_package_path)
129+
kfp_package_path=kfp_package_path,
130+
pip_trusted_hosts=pip_trusted_hosts)

sdk/python/kfp/dsl/component_factory.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class ComponentInfo():
5656
base_image: str = _DEFAULT_BASE_IMAGE
5757
packages_to_install: Optional[List[str]] = None
5858
pip_index_urls: Optional[List[str]] = None
59+
pip_trusted_hosts: Optional[List[str]] = None
5960

6061

6162
# A map from function_name to components. This is always populated when a
@@ -69,22 +70,34 @@ def _python_function_name_to_component_name(name):
6970
return name_with_spaces[0].upper() + name_with_spaces[1:]
7071

7172

72-
def make_index_url_options(pip_index_urls: Optional[List[str]]) -> str:
73+
def make_index_url_options(pip_index_urls: Optional[List[str]],
74+
pip_trusted_hosts: Optional[List[str]]) -> str:
7375
"""Generates index url options for pip install command based on provided
7476
pip_index_urls.
7577
7678
Args:
7779
pip_index_urls: Optional list of pip index urls
80+
pip_trusted_hosts: Optional list of pip trust hosts
7881
7982
Returns:
8083
- Empty string if pip_index_urls is empty/None.
81-
- '--index-url url --trusted-host url ' if pip_index_urls contains 1
84+
- '--index-url url ' if pip_index_urls contains 1
8285
url
83-
- the above followed by '--extra-index-url url --trusted-host url '
86+
- the above followed by '--extra-index-url url '
8487
for
8588
each next url in pip_index_urls if pip_index_urls contains more than 1
8689
url
8790
91+
- pip_trusted_hosts was added later. In this case, if pip_trusted_hosts is None or empty
92+
- the above followed by '--trusted-host url '
93+
for
94+
each url in pip_index_urls
95+
96+
- if pip_trusted_hosts is greater than 0
97+
- the above followed by '--trusted-host url '
98+
for
99+
each url in pip_trusted_hosts
100+
88101
Note: In case pip_index_urls is not empty, the returned string will
89102
contain space at the end.
90103
"""
@@ -94,10 +107,17 @@ def make_index_url_options(pip_index_urls: Optional[List[str]]) -> str:
94107
index_url = pip_index_urls[0]
95108
extra_index_urls = pip_index_urls[1:]
96109

97-
options = [f'--index-url {index_url} --trusted-host {index_url}']
98-
options.extend(
99-
f'--extra-index-url {extra_index_url} --trusted-host {extra_index_url}'
100-
for extra_index_url in extra_index_urls)
110+
options = [f'--index-url {index_url}']
111+
options.extend(f'--extra-index-url {extra_index_url}'
112+
for extra_index_url in extra_index_urls)
113+
114+
if pip_trusted_hosts is None or len(pip_trusted_hosts) == 0:
115+
options.extend([f'--trusted-host {index_url}'])
116+
options.extend(f'--trusted-host {extra_index_url}'
117+
for extra_index_url in extra_index_urls)
118+
else:
119+
options.extend(f'--trusted-host {trusted_host}'
120+
for trusted_host in pip_trusted_hosts)
101121

102122
return ' '.join(options) + ' '
103123

@@ -126,6 +146,7 @@ def _get_packages_to_install_command(
126146
packages_to_install: Optional[List[str]] = None,
127147
install_kfp_package: bool = True,
128148
target_image: Optional[str] = None,
149+
pip_trusted_hosts: Optional[List[str]] = None,
129150
) -> List[str]:
130151
packages_to_install = packages_to_install or []
131152
kfp_in_user_pkgs = any(pkg.startswith('kfp') for pkg in packages_to_install)
@@ -136,7 +157,8 @@ def _get_packages_to_install_command(
136157
if not inject_kfp_install and not packages_to_install:
137158
return []
138159
pip_install_strings = []
139-
index_url_options = make_index_url_options(pip_index_urls)
160+
index_url_options = make_index_url_options(pip_index_urls,
161+
pip_trusted_hosts)
140162

141163
if inject_kfp_install:
142164
if kfp_package_path:
@@ -517,6 +539,7 @@ def create_component_from_func(
517539
output_component_file: Optional[str] = None,
518540
install_kfp_package: bool = True,
519541
kfp_package_path: Optional[str] = None,
542+
pip_trusted_hosts: Optional[List[str]] = None,
520543
) -> python_component.PythonComponent:
521544
"""Implementation for the @component decorator.
522545
@@ -530,6 +553,7 @@ def create_component_from_func(
530553
kfp_package_path=kfp_package_path,
531554
packages_to_install=packages_to_install,
532555
pip_index_urls=pip_index_urls,
556+
pip_trusted_hosts=pip_trusted_hosts,
533557
)
534558

535559
command = []
@@ -575,7 +599,8 @@ def create_component_from_func(
575599
output_component_file=output_component_file,
576600
base_image=base_image,
577601
packages_to_install=packages_to_install,
578-
pip_index_urls=pip_index_urls)
602+
pip_index_urls=pip_index_urls,
603+
pip_trusted_hosts=pip_trusted_hosts)
579604

580605
if REGISTERED_MODULES is not None:
581606
REGISTERED_MODULES[component_name] = component_info

sdk/python/kfp/dsl/component_factory_test.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,24 @@ def test_with_packages_to_install_with_pip_index_url(self):
152152
'\nif ! [ -x "$(command -v pip)" ]; then\n python3 -m ensurepip || python3 -m ensurepip --user || apt-get install python3-pip\nfi\n\nPIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location --index-url https://myurl.org/simple --trusted-host https://myurl.org/simple \'kfp==2.1.3\' \'--no-deps\' \'typing-extensions>=3.7.4,<5; python_version<"3.9"\' && python3 -m pip install --quiet --no-warn-script-location --index-url https://myurl.org/simple --trusted-host https://myurl.org/simple \'package1\' \'package2\' && "$0" "$@"\n'
153153
]))
154154

155+
def test_with_packages_to_install_with_pip_index_url_and_trusted_host(self):
156+
packages_to_install = ['package1', 'package2']
157+
pip_index_urls = ['https://myurl.org/simple']
158+
pip_trusted_hosts = ['myurl.org']
159+
160+
command = component_factory._get_packages_to_install_command(
161+
packages_to_install=packages_to_install,
162+
pip_index_urls=pip_index_urls,
163+
pip_trusted_hosts=pip_trusted_hosts,
164+
)
165+
166+
self.assertEqual(
167+
strip_kfp_version(command),
168+
strip_kfp_version([
169+
'sh', '-c',
170+
'\nif ! [ -x "$(command -v pip)" ]; then\n python3 -m ensurepip || python3 -m ensurepip --user || apt-get install python3-pip\nfi\n\nPIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location --index-url https://myurl.org/simple --trusted-host myurl.org \'kfp==2.1.3\' \'--no-deps\' \'typing-extensions>=3.7.4,<5; python_version<"3.9"\' && python3 -m pip install --quiet --no-warn-script-location --index-url https://myurl.org/simple --trusted-host myurl.org \'package1\' \'package2\' && "$0" "$@"\n'
171+
]))
172+
155173

156174
class TestInvalidParameterName(unittest.TestCase):
157175

0 commit comments

Comments
 (0)