Skip to content

MINIFICPP-2606 Improve logging for python virtualenv initialization #2008

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 56 additions & 9 deletions extensions/python/PythonDependencyInstaller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
*/
#include "PythonDependencyInstaller.h"

#include <cstdio>

#include "PythonScriptException.h"
#include "PythonInterpreter.h"
#include "PyException.h"
#include "types/Types.h"
#include "utils/OptionalUtils.h"
#include "utils/ConfigurationUtils.h"

namespace org::apache::nifi::minifi::extensions::python {

Expand Down Expand Up @@ -49,6 +52,44 @@ std::string encapsulateCommandInQuotesIfNeeded(const std::string& command) {
#endif
}

#ifdef WIN32
#define popen _popen
#define pclose _pclose
#endif

struct CommandResult {
int exit_code;
std::string output;
};

CommandResult executeProcess(const std::string& command) {
std::array<char, utils::configuration::DEFAULT_BUFFER_SIZE> buffer{};

FILE* pipe = popen(encapsulateCommandInQuotesIfNeeded(command).c_str(), "r");
if (!pipe) {
return {1, fmt::format("Failed to open pipe for command: {}", command)};
}

std::ostringstream result;
while (fgets(buffer.data(), gsl::narrow<int>(buffer.size()), pipe) != nullptr) {
result << buffer.data();
}

int status = pclose(pipe);
#ifdef WIN32
int exit_code = status;
#else
int exit_code = -1;
if (WIFEXITED(status)) {
exit_code = WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
exit_code = -WTERMSIG(status);
}
#endif

return {exit_code, result.str()};
}

} // namespace

PythonDependencyInstaller::PythonDependencyInstaller(const std::shared_ptr<Configure> &configuration) {
Expand Down Expand Up @@ -93,25 +134,31 @@ void PythonDependencyInstaller::createVirtualEnvIfSpecified() const {
}
if (!std::filesystem::exists(virtualenv_path_) || std::filesystem::is_empty(virtualenv_path_)) {
logger_->log_info("Creating python virtual env at: {}", virtualenv_path_.string());
auto venv_command = "\"" + python_binary_ + "\" -m venv \"" + virtualenv_path_.string() + "\"";
auto return_value = std::system(encapsulateCommandInQuotesIfNeeded(venv_command).c_str());
if (return_value != 0) {
throw PythonScriptException(fmt::format("The following command creating python virtual env failed: '{}'", venv_command));
auto venv_command = "\"" + python_binary_ + "\" -m venv \"" + virtualenv_path_.string() + "\" 2>&1";
auto result = executeProcess(venv_command);
if (result.exit_code != 0) {
logger_->log_error("The following command creating python virtual env failed: '{}'\nSetup process output:\n{}", venv_command, result.output);
throw PythonScriptException(fmt::format("The following command creating python virtual env failed: '{}'\nSetup process output:\n{}", venv_command, result.output));
}
}
}

void PythonDependencyInstaller::runInstallCommandInVirtualenv(const std::string& install_command) const {
std::string command_with_virtualenv;
#if WIN32
command_with_virtualenv.append("\"").append((virtualenv_path_ / "Scripts" / "activate.bat").string()).append("\" && ");
command_with_virtualenv.append("\"").append((virtualenv_path_ / "Scripts" / "activate.bat").string()).append("\" 2>&1 && ");
#else
command_with_virtualenv.append(". \"").append((virtualenv_path_ / "bin" / "activate").string()).append("\" && ");
command_with_virtualenv.append(". \"").append((virtualenv_path_ / "bin" / "activate").string()).append("\" 2>&1 && ");
#endif
command_with_virtualenv.append(install_command);
auto return_value = std::system(encapsulateCommandInQuotesIfNeeded(command_with_virtualenv).c_str());
if (return_value != 0) {
throw PythonScriptException(fmt::format("The following command to install python packages failed: '{}'", command_with_virtualenv));
command_with_virtualenv.append(" 2>&1");

auto result = executeProcess(command_with_virtualenv);
if (result.exit_code != 0) {
logger_->log_error("Failed to install python packages to virtualenv with command: {}\nInstall process output:\n{}", command_with_virtualenv, result.output);
throw PythonScriptException(fmt::format("Failed to install python packages to virtualenv with command: {}\nInstall process output:\n{}", command_with_virtualenv, result.output));
} else {
logger_->log_info("Python packages installed successfully with command: '{}'.\nInstall process output:\n{}", command_with_virtualenv, result.output);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def visit_ClassDef(self, node):
for elt in detail.value.elts:
# Check if the element is a string constant and add it to the dependencies list
if isinstance(elt, ast.Constant):
self.dependencies.append(elt.s)
self.dependencies.append(elt.value)
break
break

Expand All @@ -48,7 +48,7 @@ def extract_dependencies(file_path):
print("Installing dependencies for MiNiFi python processors...")

# --no-cache-dir is used to be in line with NiFi's dependency install behavior
command = [sys.executable, "-m", "pip", "install", "--no-cache-dir"]
command = [sys.executable, "-m", "pip", "install", "--no-cache-dir", "--progress-bar", "off"]
dependencies_found = False
for i in range(1, len(sys.argv)):
if "requirements.txt" in sys.argv[i]:
Expand All @@ -63,4 +63,8 @@ def extract_dependencies(file_path):
command += dependencies

if dependencies_found:
subprocess.check_call(command)
try:
subprocess.check_call(command)
except subprocess.CalledProcessError as e:
print("Error occurred while installing dependencies: {}".format(e))
sys.exit(1)
Loading