Skip to content

Commit

Permalink
Add support for OpsGenie EU region. Fixes #294
Browse files Browse the repository at this point in the history
  • Loading branch information
cuu508 committed Oct 14, 2019
1 parent 4625196 commit 1dea8b6
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
- Send monthly reports on 1st of every month, not randomly during the month
- Signup form sets the "auto-login" cookie to avoid an extra click during first login
- Autofocus the email field in the signup form, and submit on enter key
- Add support for OpsGenie EU region (#294)

### Bug Fixes
- Prevent double-clicking the submit button in signup form
Expand Down
18 changes: 18 additions & 0 deletions hc/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,24 @@ def whatsapp_notify_down(self):
doc = json.loads(self.value)
return doc["down"]

@property
def opsgenie_key(self):
assert self.kind == "opsgenie"
if not self.value.startswith("{"):
return self.value

doc = json.loads(self.value)
return doc["key"]

@property
def opsgenie_region(self):
assert self.kind == "opsgenie"
if not self.value.startswith("{"):
return "us"

doc = json.loads(self.value)
return doc["region"]


class Notification(models.Model):
class Meta:
Expand Down
11 changes: 11 additions & 0 deletions hc/api/tests/test_channel_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,14 @@ def test_webhook_spec_handles_mixed(self):
"headers": {"X-Status": "OK"},
},
)

def test_it_handles_legacy_opsgenie_value(self):
c = Channel(kind="opsgenie", value="foo123")
self.assertEqual(c.opsgenie_key, "foo123")
self.assertEqual(c.opsgenie_region, "us")

def test_it_handles_json_opsgenie_value(self):
c = Channel(kind="opsgenie")
c.value = json.dumps({"key": "abc", "region": "eu"})
self.assertEqual(c.opsgenie_key, "abc")
self.assertEqual(c.opsgenie_region, "eu")
16 changes: 15 additions & 1 deletion hc/api/tests/test_notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ def test_hipchat(self, mock_post):
self.assertEqual(Notification.objects.count(), 0)

@patch("hc.api.transports.requests.request")
def test_opsgenie(self, mock_post):
def test_opsgenie_with_legacy_value(self, mock_post):
self._setup_data("opsgenie", "123")
mock_post.return_value.status_code = 202

Expand All @@ -431,6 +431,7 @@ def test_opsgenie(self, mock_post):

self.assertEqual(mock_post.call_count, 1)
args, kwargs = mock_post.call_args
self.assertIn("api.opsgenie.com", args[1])
payload = kwargs["json"]
self.assertIn("DOWN", payload["message"])

Expand All @@ -448,6 +449,19 @@ def test_opsgenie_up(self, mock_post):
method, url = args
self.assertTrue(str(self.check.code) in url)

@patch("hc.api.transports.requests.request")
def test_opsgenie_with_json_value(self, mock_post):
self._setup_data("opsgenie", json.dumps({"key": "456", "region": "eu"}))
mock_post.return_value.status_code = 202

self.channel.notify(self.check)
n = Notification.objects.first()
self.assertEqual(n.error, "")

self.assertEqual(mock_post.call_count, 1)
args, kwargs = mock_post.call_args
self.assertIn("api.eu.opsgenie.com", args[1])

@patch("hc.api.transports.requests.request")
def test_pushover(self, mock_post):
self._setup_data("po", "123|0")
Expand Down
20 changes: 15 additions & 5 deletions hc/api/transports.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class OpsGenie(HttpTransport):
def notify(self, check):
headers = {
"Conent-Type": "application/json",
"Authorization": "GenieKey %s" % self.channel.value,
"Authorization": "GenieKey %s" % self.channel.opsgenie_key,
}

payload = {"alias": str(check.code), "source": settings.SITE_NAME}
Expand All @@ -235,6 +235,9 @@ def notify(self, check):
payload["description"] = tmpl("opsgenie_description.html", check=check)

url = "https://api.opsgenie.com/v2/alerts"
if self.channel.opsgenie_region == "eu":
url = "https://api.eu.opsgenie.com/v2/alerts"

if check.status == "up":
url += "/%s/close?identifierType=alias" % check.code

Expand Down Expand Up @@ -468,6 +471,7 @@ def notify(self, check):

return self.post(self.URL, params=params)


class Apprise(HttpTransport):
def notify(self, check):

Expand All @@ -481,8 +485,14 @@ def notify(self, check):

a.add(self.channel.value)

notify_type = apprise.NotifyType.SUCCESS \
if check.status == "up" else apprise.NotifyType.FAILURE
notify_type = (
apprise.NotifyType.SUCCESS
if check.status == "up"
else apprise.NotifyType.FAILURE
)

return "Failed" if not \
a.notify(body=body, title=title, notify_type=notify_type) else None
return (
"Failed"
if not a.notify(body=body, title=title, notify_type=notify_type)
else None
)
3 changes: 2 additions & 1 deletion hc/front/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ class CronForm(forms.Form):

class AddOpsGenieForm(forms.Form):
error_css_class = "has-error"
value = forms.CharField(max_length=40)
region = forms.ChoiceField(initial="us", choices=(("us", "US"), ("eu", "EU")))
key = forms.CharField(max_length=40)


class AddEmailForm(forms.Form):
Expand Down
24 changes: 20 additions & 4 deletions hc/front/tests/test_add_opsgenie.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

from hc.api.models import Channel
from hc.test import BaseTestCase

Expand All @@ -11,22 +13,36 @@ def test_instructions_work(self):
self.assertContains(r, "escalation policies and incident tracking")

def test_it_works(self):
form = {"value": "123456"}
form = {"key": "123456", "region": "us"}

self.client.login(username="alice@example.org", password="password")
r = self.client.post(self.url, form)
self.assertRedirects(r, "/integrations/")

c = Channel.objects.get()
self.assertEqual(c.kind, "opsgenie")
self.assertEqual(c.value, "123456")

payload = json.loads(c.value)
self.assertEqual(payload["key"], "123456")
self.assertEqual(payload["region"], "us")
self.assertEqual(c.project, self.project)

def test_it_trims_whitespace(self):
form = {"value": " 123456 "}
form = {"key": " 123456 ", "region": "us"}

self.client.login(username="alice@example.org", password="password")
self.client.post(self.url, form)

c = Channel.objects.get()
self.assertEqual(c.value, "123456")
payload = json.loads(c.value)
self.assertEqual(payload["key"], "123456")

def test_it_saves_eu_region(self):
form = {"key": "123456", "region": "eu"}

self.client.login(username="alice@example.org", password="password")
r = self.client.post(self.url, form)

c = Channel.objects.get()
payload = json.loads(c.value)
self.assertEqual(payload["region"], "eu")
5 changes: 3 additions & 2 deletions hc/front/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1138,13 +1138,14 @@ def add_opsgenie(request):
form = AddOpsGenieForm(request.POST)
if form.is_valid():
channel = Channel(project=request.project, kind="opsgenie")
channel.value = form.cleaned_data["value"]
v = {"region": form.cleaned_data["region"], "key": form.cleaned_data["key"]}
channel.value = json.dumps(v)
channel.save()

channel.assign_all_checks()
return redirect("hc-channels")
else:
form = AddUrlForm()
form = AddOpsGenieForm()

ctx = {"page": "channels", "project": request.project, "form": form}
return render(request, "integrations/add_opsgenie.html", ctx)
Expand Down
37 changes: 33 additions & 4 deletions templates/integrations/add_opsgenie.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,42 @@ <h2>Integration Settings</h2>
id="api-key"
type="text"
class="form-control"
name="value"
name="key"
placeholder=""
value="{{ form.value.value|default:"" }}">
value="{{ form.key.value|default:"" }}">

{% if form.value.errors %}
{% if form.key.errors %}
<div class="help-block">
{{ form.value.errors|join:"" }}
{{ form.key.errors|join:"" }}
</div>
{% endif %}
</div>
</div>
<div class="form-group {{ form.region.css_classes }}">
<label for="api-key" class="col-sm-2 control-label">Region</label>
<div class="col-sm-4">
<label class="radio-container">
<input
type="radio"
name="region"
value="us"
{% if form.region.value == "us" %} checked {% endif %}>
<span class="radiomark"></span>
US (default)
</label>
<label class="radio-container">
<input
type="radio"
name="region"
value="eu"
{% if form.region.value == "eu" %} checked {% endif %}>
<span class="radiomark"></span>
EU
</label>

{% if form.region.errors %}
<div class="help-block">
{{ form.region.errors|join:"" }}
</div>
{% endif %}
</div>
Expand Down

0 comments on commit 1dea8b6

Please sign in to comment.