diff --git a/managed/devops/opscli/ybops/cloud/common/ansible.py b/managed/devops/opscli/ybops/cloud/common/ansible.py
index adbfc530d50e..f806a45ff46c 100644
--- a/managed/devops/opscli/ybops/cloud/common/ansible.py
+++ b/managed/devops/opscli/ybops/cloud/common/ansible.py
@@ -267,6 +267,7 @@ def run(self, filename, extra_vars=None, host_info=None, print_output=True,
stdout_str = stdout.decode('utf-8')
stderr_str = stderr.decode('utf-8') if rc != 0 else ""
if print_output:
+ # Write output to stderr by default.
logging.info(stdout_str)
if rc != 0:
diff --git a/managed/src/main/java/com/yugabyte/yw/common/ShellProcessHandler.java b/managed/src/main/java/com/yugabyte/yw/common/ShellProcessHandler.java
index 4aaf0247f8a1..471f076d241d 100644
--- a/managed/src/main/java/com/yugabyte/yw/common/ShellProcessHandler.java
+++ b/managed/src/main/java/com/yugabyte/yw/common/ShellProcessHandler.java
@@ -54,10 +54,10 @@ public class ShellProcessHandler {
static final Pattern ANSIBLE_FAIL_PAT =
Pattern.compile(
- "(ybops.common.exceptions.YBOpsRuntimeError: Runtime error: "
- + "Playbook run.* )with args.* (failed with.*? [0-9]+)");
+ "(ybops\\.common\\.exceptions\\.YB[^\\s]+Error:.*? Playbook run.*?)with args.* (failed"
+ + " with.*? [0-9]+)");
static final Pattern ANSIBLE_FAILED_TASK_PAT =
- Pattern.compile("TASK.*?fatal.*?FAILED.*", Pattern.DOTALL);
+ Pattern.compile("TASK\\s+\\[.+\\].*?(fatal:.*?FAILED.*|failed: (?!false).*)", Pattern.DOTALL);
static final Pattern PYTHON_ERROR_PAT =
Pattern.compile("()(.*?)()", Pattern.DOTALL);
static final String ANSIBLE_IGNORING = "ignoring";
@@ -178,11 +178,12 @@ public ShellResponse run(List command, ShellProcessContext context) {
itse);
}
response.message = (response.code == ERROR_CODE_SUCCESS) ? processOutput : processError;
- String specificErrMsg = getAnsibleErrMsg(response.code, processOutput, processError);
- if (specificErrMsg == null) {
- specificErrMsg = getPythonErrMsg(response.code, processOutput);
- }
+ String specificErrMsg = getPythonErrMsg(response.code, processOutput);
if (specificErrMsg != null) {
+ String ansibleErrMsg = getAnsibleErrMsg(response.code, specificErrMsg, processError);
+ if (ansibleErrMsg != null) {
+ specificErrMsg = ansibleErrMsg;
+ }
response.message = specificErrMsg;
}
}
@@ -390,20 +391,21 @@ private static void destroyForcibly(Process process, String description) {
}
}
- private static String getAnsibleErrMsg(int code, String stdout, String stderr) {
+ private static String getAnsibleErrMsg(int code, String pythonErrMsg, String stderr) {
- if (stderr == null || code == ERROR_CODE_SUCCESS) return null;
+ if (pythonErrMsg == null || stderr == null || code == ERROR_CODE_SUCCESS) return null;
String result = null;
- Matcher ansibleFailMatch = ANSIBLE_FAIL_PAT.matcher(stderr);
+ Matcher ansibleFailMatch = ANSIBLE_FAIL_PAT.matcher(pythonErrMsg);
if (ansibleFailMatch.find()) {
result = ansibleFailMatch.group(1) + ansibleFailMatch.group(2);
- // Attempt to find a line in ansible stdout for the failed task.
+ // By default, python logging module writes to stderr.
+ // Attempt to find a line in ansible stderr for the failed task.
// Logs for each task are separated by empty lines.
- // Some fatal failures are ignored by ansible, so skip them
- for (String s : stdout.split("\\R\\R")) {
+ // Some fatal failures are ignored by ansible, so skip them.
+ for (String s : stderr.split("\\R\\R")) {
if (s.contains(ANSIBLE_IGNORING)) {
continue;
}