Skip to content

Commit f78c8ce

Browse files
authored
feat: bash with shell (#4966)
1 parent b588c3e commit f78c8ce

File tree

4 files changed

+290
-71
lines changed

4 files changed

+290
-71
lines changed

docs/snippets/providers/bash-snippet-autogenerated.mdx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{/* This snippet is automatically generated using scripts/docs_render_provider_snippets.py
1+
{/* This snippet is automatically generated using scripts/docs_render_provider_snippets.py
22
Do not edit it manually, as it will be overwritten */}
33

44

@@ -14,8 +14,9 @@ steps:
1414
provider: bash
1515
config: "{{ provider.my_provider_name }}"
1616
with:
17-
timeout: {value}
18-
command: {value}
17+
timeout: {value}
18+
command: {value}
19+
shell: {value}
1920
```
2021
2122

keep/providers/bash_provider/bash_provider.py

Lines changed: 92 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
BashProvider is a class that implements the BaseOutputProvider.
33
"""
4+
45
import shlex
56
import subprocess
67

@@ -20,80 +21,104 @@ def validate_config(self):
2021
pass
2122

2223
def _query(
23-
self,
24-
timeout: int = 60,
25-
command: str = "",
26-
**kwargs
27-
):
24+
self, timeout: int = 60, command: str = "", shell: bool = False, **kwargs
25+
):
2826
"""Bash provider eval shell command to get results
2927
3028
Returns:
3129
_type_: _description_
3230
"""
33-
# timeout = kwargs.get("timeout", 60)
34-
# command = kwargs.get("command", "")
3531
parsed_command = self.io_handler.parse(command)
36-
# parse by pipes
37-
parsed_commands = parsed_command.split("|")
38-
# Initialize the input for the first command
39-
input_stream = None
40-
41-
processes = []
42-
43-
for cmd in parsed_commands:
44-
# Split the command string into a list of arguments
45-
cmd_args = shlex.split(cmd.strip())
46-
47-
# Run the command and pipe its output to the next command, capturing stderr
48-
process = subprocess.Popen(
49-
cmd_args,
50-
stdin=input_stream,
51-
stdout=subprocess.PIPE,
52-
stderr=subprocess.PIPE,
53-
)
54-
55-
if input_stream is not None:
56-
# Close the input_stream (output of the previous command)
57-
input_stream.close()
58-
59-
# Update input_stream to be the output of the current command
60-
input_stream = process.stdout
61-
62-
# Append the current process to the list of processes
63-
processes.append(process)
64-
65-
# Get the final output
66-
try:
67-
stdout, stderr = processes[-1].communicate(timeout=timeout)
68-
return_code = processes[-1].returncode
69-
# stdout and stderr are strings or None
70-
if stdout or stdout == b"":
71-
stdout = stdout.decode()
72-
73-
if stderr or stderr == b"":
74-
stderr = stderr.decode()
75-
except subprocess.TimeoutExpired:
76-
# use check_output to get the output of the last process
77-
# this is some MacOS bug, where communicate() doesn't work and raise TimeoutExpired (idk why but it works on Linux)
32+
33+
if shell:
34+
# Use shell=True for complex commands
35+
try:
36+
result = subprocess.run(
37+
parsed_command,
38+
shell=True,
39+
capture_output=True,
40+
timeout=timeout,
41+
text=True,
42+
)
43+
return {
44+
"stdout": result.stdout,
45+
"stderr": result.stderr,
46+
"return_code": result.returncode,
47+
}
48+
except subprocess.TimeoutExpired:
49+
try:
50+
self.logger.warning(
51+
"TimeoutExpired, using check_output - MacOS bug?"
52+
)
53+
stdout = subprocess.check_output(
54+
parsed_command,
55+
stderr=subprocess.STDOUT,
56+
timeout=timeout,
57+
shell=True,
58+
).decode()
59+
return {
60+
"stdout": stdout,
61+
"stderr": None,
62+
"return_code": 0,
63+
}
64+
except Exception as e:
65+
return {
66+
"stdout": None,
67+
"stderr": str(e),
68+
"return_code": -1,
69+
}
70+
else:
71+
# Original logic for simple commands
72+
parsed_commands = parsed_command.split("|")
73+
input_stream = None
74+
processes = []
75+
76+
for cmd in parsed_commands:
77+
cmd_args = shlex.split(cmd.strip())
78+
process = subprocess.Popen(
79+
cmd_args,
80+
stdin=input_stream,
81+
stdout=subprocess.PIPE,
82+
stderr=subprocess.PIPE,
83+
)
84+
85+
if input_stream is not None:
86+
input_stream.close()
87+
88+
input_stream = process.stdout
89+
processes.append(process)
90+
7891
try:
79-
self.logger.warning("TimeoutExpired, using check_output - MacOS bug?")
80-
stdout = subprocess.check_output(
81-
cmd, stderr=subprocess.STDOUT, timeout=timeout, shell=True
82-
).decode()
83-
stderr = None
84-
return_code = 0
85-
self.logger.warning("check_output worked")
86-
# todo: fix that
87-
except Exception as e:
88-
stdout = None
89-
stderr = e.args[1].decode()
90-
return_code = e.args[0]
91-
92-
return {
93-
"stdout": str(stdout),
94-
"stderr": str(stderr),
95-
"return_code": return_code,
96-
}
92+
stdout, stderr = processes[-1].communicate(timeout=timeout)
93+
return_code = processes[-1].returncode
94+
95+
if stdout or stdout == b"":
96+
stdout = stdout.decode()
97+
if stderr or stderr == b"":
98+
stderr = stderr.decode()
99+
except subprocess.TimeoutExpired:
100+
try:
101+
self.logger.warning(
102+
"TimeoutExpired, using check_output - MacOS bug?"
103+
)
104+
stdout = subprocess.check_output(
105+
parsed_command,
106+
stderr=subprocess.STDOUT,
107+
timeout=timeout,
108+
shell=True,
109+
).decode()
110+
stderr = None
111+
return_code = 0
112+
except Exception as e:
113+
stdout = None
114+
stderr = str(e)
115+
return_code = -1
116+
117+
return {
118+
"stdout": str(stdout),
119+
"stderr": str(stderr),
120+
"return_code": return_code,
121+
}
97122

98123
def dispose(self):
99124
"""

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "keep"
3-
version = "0.45.0"
3+
version = "0.45.1"
44
description = "Alerting. for developers, by developers."
55
authors = ["Keep Alerting LTD"]
66
packages = [{include = "keep"}]

0 commit comments

Comments
 (0)