blob: f6576176843bcd90b6c4056cd769f7eea00a9cf2 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import docker
from behave import given
from minifi_test_framework.steps import checking_steps # noqa: F401
from minifi_test_framework.steps import configuration_steps # noqa: F401
from minifi_test_framework.steps import core_steps # noqa: F401
from minifi_test_framework.steps import flow_building_steps # noqa: F401
from minifi_test_framework.core.minifi_test_context import MinifiTestContext
from minifi_test_framework.containers.docker_image_builder import DockerImageBuilder
class PythonWithDependenciesOptions:
REQUIREMENTS_FILE = "apacheminificpp-python-requirements-file:latest"
SYSTEM_INSTALLED_PACKAGES = "apacheminificpp-python-system-installed-packages:latest"
INLINE_DEFINED_PACKAGES = "apacheminificpp-python-inline-defined-packages:latest"
def build_minifi_cpp_image_with_nifi_python_processors_using_dependencies(context, python_option):
minifi_tag_prefix = os.environ['MINIFI_TAG_PREFIX'] if 'MINIFI_TAG_PREFIX' in os.environ else ""
client: docker.DockerClient = docker.from_env()
is_fhs = 'MINIFI_INSTALLATION_TYPE=FHS' in str(client.images.get(context.minifi_container_image).history())
minifi_python_dir_path = '/var/lib/nifi-minifi-cpp/minifi-python' if is_fhs else '/opt/minifi/minifi-current/minifi-python'
minifi_python_venv_parent = '/var/lib/nifi-minifi-cpp' if is_fhs else '/opt/minifi/minifi-current'
parse_document_url = "https://raw.githubusercontent.com/apache/nifi-python-extensions/refs/heads/main/src/extensions/chunking/ParseDocument.py"
chunk_document_url = "https://raw.githubusercontent.com/apache/nifi-python-extensions/refs/heads/main/src/extensions/chunking/ChunkDocument.py"
pip3_install_command = ""
requirements_install_command = ""
additional_cmd = ""
# The following sed command is used to remove the existing dependencies from the ParseDocument and ChunkDocument processors
# /class ProcessorDetails:/,/^$/: Do the following between 'class ProcessorDetails:' and the first empty line (so we don't modify other PropertyDescriptor blocks below)
# /^\s*dependencies\s*=/,/\]\s*$/: Do the following between 'dependencies =' at the start of a line, and ']' at the end of a line
# d: Delete line
parse_document_sed_cmd = 'sed -i "/class ProcessorDetails:/,/^$/{{/^\\s*dependencies\\s*=/,/\\]\\s*$/d}}" {minifi_python_dir}/nifi_python_processors/ParseDocument.py && \\'.format(minifi_python_dir=minifi_python_dir_path)
chunk_document_sed_cmd = 'sed -i "/class ProcessorDetails:/,/^$/{{/^\\s*dependencies\\s*=/,/\\]\\s*$/d}}" {minifi_python_dir}/nifi_python_processors/ChunkDocument.py && \\'.format(minifi_python_dir=minifi_python_dir_path)
if python_option == PythonWithDependenciesOptions.SYSTEM_INSTALLED_PACKAGES:
if not minifi_tag_prefix or "bookworm" in minifi_tag_prefix or "noble" in minifi_tag_prefix or "trixie" in minifi_tag_prefix:
additional_cmd = "RUN pip3 install --break-system-packages 'langchain<=0.17.0'"
else:
additional_cmd = "RUN pip3 install 'langchain<=0.17.0'"
elif python_option == PythonWithDependenciesOptions.REQUIREMENTS_FILE:
requirements_install_command = "echo 'langchain<=0.17.0' > {minifi_python_dir}/nifi_python_processors/requirements.txt && \\".format(minifi_python_dir=minifi_python_dir_path)
elif python_option == PythonWithDependenciesOptions.INLINE_DEFINED_PACKAGES:
parse_document_sed_cmd = parse_document_sed_cmd[:-2] + ' sed -i "s/langchain==[0-9.]\\+/langchain<=0.17.0/" {minifi_python_dir}/nifi_python_processors/ParseDocument.py && \\'.format(minifi_python_dir=minifi_python_dir_path)
chunk_document_sed_cmd = 'sed -i "s/\\[\\\'langchain\\\'\\]/\\[\\\'langchain<=0.17.0\\\'\\]/" {minifi_python_dir}/nifi_python_processors/ChunkDocument.py && \\'.format(minifi_python_dir=minifi_python_dir_path)
if not minifi_tag_prefix:
pip3_install_command = "RUN apk --update --no-cache add py3-pip"
dockerfile = """\
FROM {base_image}
USER root
{pip3_install_command}
{additional_cmd}
USER minificpp
RUN wget {parse_document_url} --directory-prefix={minifi_python_dir}/nifi_python_processors && \\
wget {chunk_document_url} --directory-prefix={minifi_python_dir}/nifi_python_processors && \\
echo 'langchain<=0.17.0' > {minifi_python_dir}/nifi_python_processors/requirements.txt && \\
{requirements_install_command}
{parse_document_sed_cmd}
{chunk_document_sed_cmd}
python3 -m venv {minifi_python_venv_parent}/venv && \\
python3 -m venv {minifi_python_venv_parent}/venv-with-langchain && \\
. {minifi_python_venv_parent}/venv-with-langchain/bin/activate && python3 -m pip install --no-cache-dir "langchain<=0.17.0" && \\
deactivate
""".format(base_image=context.minifi_container_image,
pip3_install_command=pip3_install_command,
parse_document_url=parse_document_url,
chunk_document_url=chunk_document_url,
additional_cmd=additional_cmd,
requirements_install_command=requirements_install_command,
parse_document_sed_cmd=parse_document_sed_cmd,
chunk_document_sed_cmd=chunk_document_sed_cmd,
minifi_python_dir=minifi_python_dir_path,
minifi_python_venv_parent=minifi_python_venv_parent)
builder = DockerImageBuilder(
image_tag=python_option,
dockerfile_content=dockerfile,
)
builder.build()
context.get_or_create_default_minifi_container().image_name = python_option
@given("the example MiNiFi python processors are present")
def step_impl(context: MinifiTestContext):
context.get_or_create_default_minifi_container().add_example_python_processors()
@given("python with langchain is installed on the MiNiFi agent {install_mode}")
def step_impl(context, install_mode):
client: docker.DockerClient = docker.from_env()
is_fhs = 'MINIFI_INSTALLATION_TYPE=FHS' in str(client.images.get(context.minifi_container_image).history())
minifi_python_venv_parent = '/var/lib/nifi-minifi-cpp' if is_fhs else '/opt/minifi/minifi-current'
if install_mode == "with required python packages":
build_minifi_cpp_image_with_nifi_python_processors_using_dependencies(context, PythonWithDependenciesOptions.SYSTEM_INSTALLED_PACKAGES)
context.get_or_create_default_minifi_container().set_property("nifi.python.install.packages.automatically", "false")
elif install_mode == "with a pre-created virtualenv":
build_minifi_cpp_image_with_nifi_python_processors_using_dependencies(context, PythonWithDependenciesOptions.REQUIREMENTS_FILE)
context.get_or_create_default_minifi_container().set_property("nifi.python.virtualenv.directory", f"{minifi_python_venv_parent}/venv")
context.get_or_create_default_minifi_container().set_property("nifi.python.install.packages.automatically", "true")
elif install_mode == "with a pre-created virtualenv containing the required python packages":
build_minifi_cpp_image_with_nifi_python_processors_using_dependencies(context, PythonWithDependenciesOptions.REQUIREMENTS_FILE)
context.get_or_create_default_minifi_container().set_property("nifi.python.virtualenv.directory", f"{minifi_python_venv_parent}/venv-with-langchain")
context.get_or_create_default_minifi_container().set_property("nifi.python.install.packages.automatically", "false")
elif install_mode == "using inline defined Python dependencies to install packages":
build_minifi_cpp_image_with_nifi_python_processors_using_dependencies(context, PythonWithDependenciesOptions.INLINE_DEFINED_PACKAGES)
context.get_or_create_default_minifi_container().set_property("nifi.python.virtualenv.directory", f"{minifi_python_venv_parent}/venv")
context.get_or_create_default_minifi_container().set_property("nifi.python.install.packages.automatically", "true")
else:
raise Exception("Unknown python install mode.")