Skip to content
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

Playbook generator #1136

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e40e34c
Feature: Adding playbook builder
pavangudiwada Jul 25, 2023
7a9d084
initial commit - actions part is broken
aantn Jul 28, 2023
c10a10c
get actions working
aantn Jul 28, 2023
ee6e93f
fixing wrong functions (#1006)
Avi-Robusta Jul 25, 2023
3253479
feat(helm): update kube-prometheus-stack to 47.2.0 (#964)
lippertmarkus Jul 26, 2023
6ec5ec2
add dropdown for prom crd error
RoiGlinik Jul 26, 2023
14126d9
Main page with sub pages but slow
pavangudiwada Sep 20, 2023
edaaf7b
recurssion issue - dont use
pavangudiwada Sep 21, 2023
37a0269
Working navigation between pages
pavangudiwada Oct 17, 2023
b55102c
Added new predefined playbooks
pavangudiwada Oct 26, 2023
1eb944e
Fixed ingress playbook
pavangudiwada Oct 27, 2023
368391c
Merge branch 'master' into natan-streamlit-triggers-test1
aantn Oct 27, 2023
b595e09
Update poetry.lock
aantn Oct 27, 2023
ab0195a
Fixed session_state code and variable name
pavangudiwada Oct 27, 2023
9e06e99
Removed extra methods, updated session variables, added a back button
pavangudiwada Oct 27, 2023
f8ba263
Updated deprecated code
pavangudiwada Oct 27, 2023
18ccbd1
simplify logic for expander_state
aantn Oct 28, 2023
0cce345
Improve pydantic models to work better w/ playbooks generator
aantn Oct 28, 2023
3fe1cd8
Improvements to playbook generator
aantn Oct 28, 2023
feb0d88
Updated Playbook text
pavangudiwada Oct 30, 2023
9a6fee8
Single page playbook generator, simple navigation (#1140)
pavangudiwada Nov 4, 2023
29de126
more schema fixes
aantn Nov 4, 2023
d17d25c
Fix helm triggers
aantn Nov 5, 2023
027a244
Update playbook_generator.py
aantn Nov 5, 2023
230e780
Use streamlit_antd_components for stepper and streamlit_extra for sty…
aantn Nov 6, 2023
2c626d3
Use pydantic_form instead of streamlit_extras.stylable_container
aantn Nov 6, 2023
723d077
Fix dependencies
aantn Nov 6, 2023
16a07a7
clarify comment
aantn Nov 6, 2023
0ebcad5
Switch back to styled container and not pydantic_form
aantn Nov 13, 2023
241d747
Fix bug with Warning events trigger
aantn Nov 13, 2023
dd98818
Issue with NonType
pavangudiwada Nov 16, 2023
bc46b1d
Fix a few bugs (not the main bug we're troubleshooting)
aantn Nov 17, 2023
633c942
Fix bug with trigger_data being None
aantn Nov 17, 2023
9a03a42
Give variable a better name
aantn Nov 20, 2023
7aef5a0
Fix bug w/ yaml output
aantn Nov 20, 2023
a9eb789
Fix bug when creating a playbook from scratch, not according to templ…
aantn Nov 20, 2023
2e9c342
Fix another bug rendering triggers to yaml
aantn Nov 20, 2023
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
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10
3,214 changes: 2,054 additions & 1,160 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ robusta = "robusta.cli.main:app"


[tool.poetry.dependencies]
python = "^3.8"
python = "^3.10"
typer = "^0.4.1"
colorlog = "^5.0.1"
pydantic = "^1.8.1"
Expand Down Expand Up @@ -112,6 +112,10 @@ all = ["Flask", "grafana-api", "watchdog", "dulwich", "better-exceptions", "Cair
[tool.poetry.group.dev.dependencies]
sphinx-jinja = { git = "https://github.com/robusta-dev/sphinx-jinja.git" }
sphinx-reredirects = "^0.1.1"
streamlit = "^1.25.0"
streamlit-pydantic = "^0.6.0"
streamlit-extras = "^0.3.4"
streamlit-antd-components = "^0.2.3"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
90 changes: 90 additions & 0 deletions scripts/custom_streamlit_pydantic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# based on code in streamlit_pydantic.pydantic_form but with extra modifications for our use case
import streamlit as st
from typing import Type, Optional, TypeVar, Callable
from pydantic import BaseModel, ValidationError, parse_obj_as
from streamlit_pydantic import pydantic_input
from streamlit_pydantic.ui_renderer import GroupOptionalFieldsStrategy
from streamlit_extras.stylable_container import stylable_container

# Define generic type to allow autocompletion for the model fields
T = TypeVar("T", bound=BaseModel)


def modified_pydantic_form(
key: str,
model: Type[T],
submit_label: str = "Submit",
clear_on_submit: bool = False,
group_optional_fields: GroupOptionalFieldsStrategy = "no", # type: ignore
lowercase_labels: bool = False,
ignore_empty_values: bool = False,
title: str = None,
on_submit: Callable = None
) -> Optional[T]:
"""Auto-generates a Streamlit form based on the given (Pydantic-based) input class.

Args:
key (str): A string that identifies the form. Each form must have its own key.
model (Type[BaseModel]): The input model. Either a class or instance based on Pydantic `BaseModel` or Python `dataclass`.
submit_label (str): A short label explaining to the user what this button is for. Defaults to “Submit”.
clear_on_submit (bool): If True, all widgets inside the form will be reset to their default values after the user presses the Submit button. Defaults to False.
group_optional_fields (str, optional): If `sidebar`, optional input elements will be rendered on the sidebar.
If `expander`, optional input elements will be rendered inside an expander element. Defaults to `no`.
lowercase_labels (bool): If `True`, all input element labels will be lowercased. Defaults to `False`.
ignore_empty_values (bool): If `True`, empty values for strings and numbers will not be stored in the session state. Defaults to `False`.

Returns:
Optional[BaseModel]: An instance of the given input class,
if the submit button is used and the input data passes the Pydantic validation.
"""
# we can't use pydantic_form because of https://github.com/LukasMasuch/streamlit-pydantic/issues/39
# so we design our own container for all elements, just like a form would
with stylable_container(
key=key,
css_styles="""
{
border: 1px solid rgba(49, 51, 63, 0.2);
border-radius: 0.5rem;
padding: calc(1em - 1px);
box-sizing: content-box;
}
""",
):
if title is not None:
st.subheader(title)

input_state = pydantic_input(
key,
model,
group_optional_fields=group_optional_fields,
lowercase_labels=lowercase_labels,
ignore_empty_values=ignore_empty_values,
)
submit_button = st.button(label=submit_label)

try:
result = None
# check if the model is an instance before parsing
if isinstance(model, BaseModel):
result = parse_obj_as(model.__class__, input_state)
else:
result = parse_obj_as(model, input_state)

if submit_button and on_submit is not None:
on_submit()
return result

except ValidationError as ex:
if not submit_button:
return None
error_text = "**Whoops! There were some problems with your input:**"
for error in ex.errors():
if "loc" in error and "msg" in error:
location = ".".join(error["loc"]).replace("__root__.", "") # type: ignore
error_msg = f"**{location}:** " + error["msg"]
error_text += "\n\n" + error_msg
else:
# Fallback
error_text += "\n\n" + str(error)
st.error(error_text)
return None
25 changes: 8 additions & 17 deletions scripts/generate_kubernetes_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ def autogenerate_triggers(f: TextIO):
f.write(
textwrap.dedent(
"""\
from typing import Optional, Dict
from typing import Optional, Dict, Literal
from pydantic import BaseModel
from robusta.integrations.kubernetes.base_triggers import K8sBaseTrigger
from robusta.core.model.k8s_operation_type import K8sOperationType
Expand All @@ -357,14 +357,8 @@ def autogenerate_triggers(f: TextIO):
textwrap.dedent(
f"""\
class {resource}{get_trigger_class_name(trigger_name)}Trigger(K8sBaseTrigger):
def __init__(self, name_prefix: str = None, namespace_prefix: str = None, labels_selector: str = None):
super().__init__(
kind=\"{resource}\",
operation={operation_type},
name_prefix=name_prefix,
namespace_prefix=namespace_prefix,
labels_selector=labels_selector,
)
kind: Literal[\"{resource}\"] = \"{resource}\"
operation: Literal[{operation_type}] = {operation_type}

@staticmethod
def get_execution_event_type() -> type:
Expand All @@ -387,14 +381,8 @@ def get_execution_event_type() -> type:
textwrap.dedent(
f"""\
class KubernetesAny{get_trigger_class_name(trigger_name)}Trigger(K8sBaseTrigger):
def __init__(self, name_prefix: str = None, namespace_prefix: str = None, labels_selector: str = None):
super().__init__(
kind=\"Any\",
operation={operation_type},
name_prefix=name_prefix,
namespace_prefix=namespace_prefix,
labels_selector=labels_selector,
)
kind: Literal[\"{resource}\"] = \"{resource}\"
operation: Literal[{operation_type}] = {operation_type}

@staticmethod
def get_execution_event_type() -> type:
Expand Down Expand Up @@ -451,3 +439,6 @@ def main():

if __name__ == "__main__":
main()
print("Done. Before committing, it is recommended to run:")
print("poetry run isort src/robusta/integrations/kubernetes/autogenerated")
print("poetry run black src/robusta/integrations/kubernetes/autogenerated")
44 changes: 27 additions & 17 deletions scripts/generate_playbook_descriptions.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from typing import Callable

from pydantic import BaseModel
from robusta.api import Action

from robusta.core.playbooks.generation import ExamplesGenerator

class PlaybookDescription(BaseModel):
function_name: str
Expand All @@ -32,13 +34,12 @@ def get_params_schema(func):
return action_params.schema()


def load_scripts(scripts_root):
# install_requirements(os.path.join(scripts_root, 'requirements.txt'))

def find_playbook_actions(scripts_root):
python_files = glob.glob(f"{scripts_root}/*.py")
actions = []

for script in python_files:
print(f"loading playbooks {script}")
print(f"found playbook file: {script}")
filename = os.path.basename(script)
(module_name, ext) = os.path.splitext(filename)
spec = importlib.util.spec_from_file_location(module_name, script)
Expand All @@ -47,26 +48,35 @@ def load_scripts(scripts_root):

playbooks = inspect.getmembers(
module,
lambda f: inspect.isfunction(f) and getattr(f, "__playbook", None) is not None,
lambda f: Action.is_action(f),
)
for _, func in playbooks:
description = PlaybookDescription(
function_name=func.__name__,
builtin_trigger_params=func.__playbook["default_trigger_params"],
docs=inspect.getdoc(func),
src=inspect.getsource(func),
src_file=inspect.getsourcefile(func),
action_params=get_params_schema(func),
)
print(description.json(), "\n\n")
print("found playbook", func)
action = Action(func)
actions.append(action)

#description = PlaybookDescription(
# function_name=func.__name__,
# builtin_trigger_params=func.__playbook["default_trigger_params"],
# docs=inspect.getdoc(func),
# src=inspect.getsource(func),
# src_file=inspect.getsourcefile(func),
# action_params=get_params_schema(func),
#)
#print(description.json(), "\n\n")

return actions


def main():
# TODO Arik - Need to be fixed in order to expose actions schema
parser = argparse.ArgumentParser(description="Generate playbook descriptions")
parser.add_argument("directory", type=str, help="directory containing the playbooks")
parser.add_argument("--directory", type=str, help="directory containing the playbooks", default="./playbooks/robusta_playbooks")
args = parser.parse_args()
load_scripts(args.directory)
actions = find_playbook_actions(args.directory)
generator = ExamplesGenerator()
triggers = generator.get_all_triggers()
trigger_to_actions = generator.get_triggers_to_actions(actions)
print(trigger_to_actions)


if __name__ == "__main__":
Expand Down
14 changes: 14 additions & 0 deletions scripts/main_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pages import demo_playbooks, playbook_builder
from streamlit import session_state as ss

if "current_page" not in ss:
ss.current_page = "demo_playbooks"

if "playbook_chosen" not in ss:
ss.playbook_chosen = False

if ss.current_page == "demo_playbooks" and not ss.playbook_chosen:
demo_playbooks.display_demo_playbook()

elif ss.current_page == "playbook_builder":
playbook_builder.display_playbook_builder()
Loading
Loading