Skip to content

Commit

Permalink
feat(render): add click-to-deploy (#57)
Browse files Browse the repository at this point in the history
Signed-off-by: talboren <[email protected]>
  • Loading branch information
talboren authored Feb 22, 2023
1 parent 6f5a9bc commit 95890bb
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 51 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/lint-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,25 @@ jobs:
uses: amannn/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: marocchino/sticky-pull-request-comment@v2
# When the previous steps fails, the workflow would stop. By adding this
# condition you can continue the execution with the populated error message.
if: always() && (steps.lint_pr_title.outputs.error_message != null)
with:
header: pr-title-lint-error
message: |
Hey there and thank you for opening this pull request! 👋🏼
We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
Details:
```
${{ steps.lint_pr_title.outputs.error_message }}
```
# Delete a previous comment when the issue has been resolved
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@v2
with:
header: pr-title-lint-error
delete: true
20 changes: 20 additions & 0 deletions .github/workflows/run-keep.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: "Run Keep / Demo"

on:
schedule:
- cron: '0 0 * * *' # run every day at 12:00 AM
workflow_dispatch:

jobs:
run:
name: run-keep
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3 # checkout keep repo
- uses: actions/setup-python@v4 # install python 3.11.1
with:
python-version: '3.11.1'
- run: python -m pip install . # install requirements
- run: keep -v run --alerts-file examples/alerts/db_disk_space.yml
env:
KEEP_PROVIDER_SLACK_DEMO: ${{ secrets.SLACK_DEMO_PROVIDER }}
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,36 @@ keep run --alerts-file examples/alerts/db_disk_space.yml


### Docker

Configure the Slack provider (See "[Run locally](https://github.com/keephq/keep#from-now-on-keep-should-be-installed-locally-and-accessible-from-your-cli-test-it-by-executing)" on how to obtain the webhook URL)

```bash
# Configure the Slack provider (you'll need the webhook url)
docker run -v ${PWD}:/app -it keephq/cli config provider --provider-type slack --provider-id slack-demo
# Run Keep
docker run -v ${PWD}:/app -it keephq/cli -j run --alerts-file examples/alerts/db_disk_space.yml
```

You should now have a providers.yaml file created locally

Run Keep and execute our example "Paper DB has insufficient disk space" alert

```bash
docker run -v ${PWD}:/app -it keephq/cli -j run --alert-url https://raw.githubusercontent.com/keephq/keep/main/examples/alerts/db_disk_space.yml
```

### Render
Click the Deploy to Render button to deploy Keep as a background worker running in [Render](https://www.render.com)

[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy)

To run Keep and execute our example "Paper DB has insufficient disk space" alert, you will need to configure you Slack provider.
<br />
When clicking the Deploy to Render button, you will be asked to provide the `KEEP_PROVIDER_SLACK_DEMO` environment variable, this is the expected format:

```json
{"authentication": {"webhook_url": "https://hooks.slack.com/services/..."}}
```

\* Refer to [Run locally](https://github.com/keephq/keep#from-now-on-keep-should-be-installed-locally-and-accessible-from-your-cli-test-it-by-executing) on how to obtain the webhook URL

##### Wanna have your alerts up and running in production? Go through our more detailed [Deployment Guide](https://keephq.wiki/deployment)

## 🔍 Learn more
Expand Down
33 changes: 27 additions & 6 deletions docs/docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,35 @@ sidebar_position: 3

# Deployment

## 👷🏻‍♀️ Under Construction
## Docker

```diff
- We are currently working on a Dockerfile and various "Click-to-deploy" buttons, coming soon!
Configure the Slack provider (See "[Run locally](https://github.com/keephq/keep#from-now-on-keep-should-be-installed-locally-and-accessible-from-your-cli-test-it-by-executing)" on how to obtain the webhook URL)

```bash
docker run -v ${PWD}:/app -it keephq/cli config provider --provider-type slack --provider-id slack-demo
```

You should now have a providers.yaml file created locally

Run Keep and execute our example "Paper DB has insufficient disk space" alert

```bash
docker run -v ${PWD}:/app -it keephq/cli -j run --alert-url https://raw.githubusercontent.com/keephq/keep/main/examples/alerts/db_disk_space.yml
```

## Render
Click the Deploy to Render button to deploy Keep as a background worker running in [Render](https://www.render.com)

[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy)

To run Keep and execute our example "Paper DB has insufficient disk space" alert, you will need to configure you Slack provider.
<br />
When clicking the Deploy to Render button, you will be asked to provide the `KEEP_PROVIDER_SLACK_DEMO` environment variable, this is the expected format:

```json
{"authentication": {"webhook_url": "https://hooks.slack.com/services/..."}}
```

Currently, Keep only works as a command-line interface (CLI).
In a real-world production scenario where on-going monitoring is required, Keep needs to be deployed as a daemon.
This can be achieved in various ways such as cron, serverless computing platforms (e.g. Vercel, fly.io, Render, Cloud Run, ECS, etc.), or dedicated servers.
\* Refer to [Run locally](https://github.com/keephq/keep#from-now-on-keep-should-be-installed-locally-and-accessible-from-your-cli-test-it-by-executing) on how to obtain the webhook URL

#### Want to deploy Keep on a specific platform? [Just open an issue](https://github.com/keephq/keep/issues/new?assignees=&labels=&template=feature_request.md&title=feature:%20new%20deployment%20option) and we will get to it ASAP!
11 changes: 1 addition & 10 deletions docs/docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@ sidebar_position: 2
## Run locally
Try our first mock alert and get it up and running in <5 minutes - Ready? Let's Go! ⏰

### Docker
Configure:
```
docker run -v ${PWD}:/app -it keep config provider --provider-type slack --provider-id slack-demo
```
Run:
```
docker run -v ${PWD}:/app -it keephq/cli -j run --alerts-file examples/alerts/db_disk_space.yml
```
### Clone and install

<h5>First, clone Keep repository:</h5>
Expand Down Expand Up @@ -56,7 +47,7 @@ keep run --alerts-file examples/alerts/db_disk_space.yml

<h5>Congrats 🥳 You should have received your first "Dunder Mifflin Paper Company" alert in Slack by now.</h5>

Wanna have your alerts up and running in production? Go through our more detailed [Getting Started Guide](https://keephq.wiki/getting-started).
Wanna have your alerts up and running in production? Go through our more detailed [Deployment Guide](https://keephq.wiki/deployment).

## Auto Completion

Expand Down
43 changes: 36 additions & 7 deletions keep/alertmanager/alertmanager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import os
import time

from keep.parser.parser import Parser

Expand All @@ -9,21 +10,49 @@ def __init__(self):
self.parser = Parser()
self.logger = logging.getLogger(__name__)

def run(self, alert: str, providers_file: str = None):
def run(
self,
alerts_path: str | list[str],
providers_file: str = None,
interval: int = 0,
):
"""
Run alerts from a file or directory.
Args:
alert (str): Either a an alert yaml or a directory containing alert yamls.
alert (str): Either a an alert yaml or a directory containing alert yamls or a list of urls to get the alerts from.
providers_file (str, optional): The path to the providers yaml. Defaults to None.
"""
self.logger.info(f"Running alert(s) from {alert}")
if os.path.isdir(alert):
self.run_from_directory(alert, providers_file)
self.logger.info(
f"Running alert(s) from {alerts_path}", extra={"interval": interval}
)
# If interval is set, run the alert every INTERVAL seconds until the user stops the process
if interval > 0:
self.logger.info(
"Running in interval mode. Press Ctrl+C to stop the process."
)
while True:
self._run(alerts_path, providers_file)
self.logger.info(f"Sleeping for {interval} seconds...")
time.sleep(interval)
# If interval is not set, run the alert once
else:
alert = self.parser.parse(alert, providers_file)
self._run(alerts_path, providers_file)
self.logger.info(
f"Alert(s) from {alerts_path} ran successfully",
extra={"interval": interval},
)

def _run(self, alert_path: str | list[str], providers_file: str = None):
if isinstance(alert_path, tuple):
for alert_url in alert_path:
alert = self.parser.parse(alert_url, providers_file)
alert.run()
elif os.path.isdir(alert_path):
self.run_from_directory(alert_path, providers_file)
else:
alert = self.parser.parse(alert_path, providers_file)
alert.run()
self.logger.info(f"Alert(s) from {alert} ran successfully")

def run_from_directory(self, alerts_dir: str, providers_file: str = None):
"""
Expand Down
41 changes: 32 additions & 9 deletions keep/cli/cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import logging
import logging.config
import sys
from collections import OrderedDict
from dataclasses import fields
from importlib import metadata

Expand All @@ -10,6 +9,7 @@
from dotenv import find_dotenv, load_dotenv

from keep.alertmanager.alertmanager import AlertManager
from keep.cli.click_extensions import NotRequiredIf
from keep.providers.providers_factory import ProvidersFactory

load_dotenv(find_dotenv())
Expand Down Expand Up @@ -106,15 +106,30 @@ def version():
@cli.command()
@click.option(
"--alerts-file",
"-f",
"-af",
type=click.Path(exists=True, dir_okay=True, file_okay=True),
help="The path to the alert yaml/alerts directory",
required=True,
)
@click.option(
"--alert-url",
"-au",
help="A url that can be used to download an alert yaml",
cls=NotRequiredIf,
multiple=True,
not_required_if="alerts_file",
)
@click.option(
"--interval",
"-i",
type=int,
help="When interval is set, Keep will run the alert every INTERVAL seconds",
required=False,
default=0,
)
@click.option(
"--providers-file",
"-p",
type=click.Path(exists=True),
type=click.Path(exists=False),
help="The path to the providers yaml",
required=False,
default="providers.yaml",
Expand All @@ -127,18 +142,26 @@ def version():
default="https://s.keephq.dev",
)
@pass_info
def run(info: Info, alerts_file, providers_file, api_key, api_url):
def run(
info: Info,
alerts_file: str,
alert_url: list[str],
interval: int,
providers_file,
api_key,
api_url,
):
"""Run the alert."""
logger.debug(f"Running alert in {alerts_file}")
logger.debug(f"Running alert in {alerts_file or alert_url}")
alert_manager = AlertManager()
try:
alert_manager.run(alerts_file, providers_file)
alert_manager.run(alerts_file or alert_url, providers_file, interval=interval)
except Exception as e:
logger.error(f"Error running alert {alerts_file}: {e}")
logger.error(f"Error running alert {alerts_file or alert_url}: {e}")
if info.verbose:
raise e
sys.exit(1)
logger.debug(f"Alert in {alerts_file} ran successfully")
logger.debug(f"Alert in {alerts_file or alert_url} ran successfully")


@cli.command()
Expand Down
32 changes: 32 additions & 0 deletions keep/cli/click_extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import click


class NotRequiredIf(click.Option):
"""
https://stackoverflow.com/questions/44247099/click-command-line-interfaces-make-options-required-if-other-optional-option-is
"""

def __init__(self, *args, **kwargs):
self.not_required_if = kwargs.pop("not_required_if")
assert self.not_required_if, "'not_required_if' parameter required"
kwargs["help"] = (
kwargs.get("help", "")
+ " NOTE: This argument is mutually exclusive with %s"
% self.not_required_if
).strip()
super(NotRequiredIf, self).__init__(*args, **kwargs)

def handle_parse_result(self, ctx, opts, args):
we_are_present = self.name in opts
other_present = self.not_required_if in opts

if other_present is False:
if we_are_present is False:
raise click.UsageError(
"Illegal usage: `%s` is required when `%s` is not provided"
% (self.name, self.not_required_if)
)
else:
self.prompt = None

return super(NotRequiredIf, self).handle_parse_result(ctx, opts, args)
Loading

1 comment on commit 95890bb

@vercel
Copy link

@vercel vercel bot commented on 95890bb Feb 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.