Browse Source

API call for updating checks

pull/114/head
Pēteris Caune 8 years ago
parent
commit
20b046cba7
18 changed files with 290 additions and 35 deletions
  1. +2
    -0
      hc/api/models.py
  2. +4
    -1
      hc/api/tests/test_list_checks.py
  3. +14
    -0
      hc/api/tests/test_pause.py
  4. +77
    -0
      hc/api/tests/test_update_check.py
  5. +1
    -0
      hc/api/urls.py
  6. +57
    -25
      hc/api/views.py
  7. +2
    -0
      hc/front/management/commands/pygmentize.py
  8. +105
    -1
      templates/front/docs_api.html
  9. +2
    -1
      templates/front/snippets/create_check_response.html
  10. +2
    -1
      templates/front/snippets/create_check_response.txt
  11. +4
    -2
      templates/front/snippets/list_checks_response.html
  12. +4
    -2
      templates/front/snippets/list_checks_response.txt
  13. +2
    -1
      templates/front/snippets/pause_check_response.html
  14. +2
    -1
      templates/front/snippets/pause_check_response.txt
  15. +4
    -0
      templates/front/snippets/update_check_request_a.html
  16. +3
    -0
      templates/front/snippets/update_check_request_a.txt
  17. +3
    -0
      templates/front/snippets/update_check_request_b.html
  18. +2
    -0
      templates/front/snippets/update_check_request_b.txt

+ 2
- 0
hc/api/models.py View File

@ -144,11 +144,13 @@ class Check(models.Model):
return [t.strip() for t in self.tags.split(" ") if t.strip()]
def to_dict(self):
update_rel_url = reverse("hc-api-update", args=[self.code])
pause_rel_url = reverse("hc-api-pause", args=[self.code])
result = {
"name": self.name,
"ping_url": self.url(),
"update_url": settings.SITE_ROOT + update_rel_url,
"pause_url": settings.SITE_ROOT + pause_rel_url,
"tags": self.tags,
"grace": int(self.grace.total_seconds()),


+ 4
- 1
hc/api/tests/test_list_checks.py View File

@ -48,7 +48,10 @@ class ListChecksTestCase(BaseTestCase):
self.assertEqual(checks["Alice 1"]["last_ping"], self.now.isoformat())
self.assertEqual(checks["Alice 1"]["n_pings"], 1)
self.assertEqual(checks["Alice 1"]["status"], "new")
pause_url = "{0}/api/v1/checks/%s/pause".format(getattr(settings, "SITE_ROOT")) % self.a1.code
update_url = settings.SITE_ROOT + "/api/v1/checks/%s" % self.a1.code
pause_url = update_url + "/pause"
self.assertEqual(checks["Alice 1"]["update_url"], update_url)
self.assertEqual(checks["Alice 1"]["pause_url"], pause_url)
next_ping = self.now + td(seconds=3600)


+ 14
- 0
hc/api/tests/test_pause.py View File

@ -32,3 +32,17 @@ class PauseTestCase(BaseTestCase):
HTTP_X_API_KEY="abc")
self.assertEqual(r.status_code, 400)
def test_it_validates_uuid(self):
url = "/api/v1/checks/not-uuid/pause"
r = self.client.post(url, "", content_type="application/json",
HTTP_X_API_KEY="abc")
self.assertEqual(r.status_code, 400)
def test_it_handles_missing_check(self):
url = "/api/v1/checks/07c2f548-9850-4b27-af5d-6c9dc157ec02/pause"
r = self.client.post(url, "", content_type="application/json",
HTTP_X_API_KEY="abc")
self.assertEqual(r.status_code, 400)

+ 77
- 0
hc/api/tests/test_update_check.py View File

@ -0,0 +1,77 @@
import json
from hc.api.models import Channel, Check
from hc.test import BaseTestCase
class UpdateCheckTestCase(BaseTestCase):
def setUp(self):
super(UpdateCheckTestCase, self).setUp()
self.check = Check(user=self.alice)
self.check.save()
def post(self, code, data):
url = "/api/v1/checks/%s" % code
r = self.client.post(url, json.dumps(data),
content_type="application/json")
return r
def test_it_works(self):
r = self.post(self.check.code, {
"api_key": "abc",
"name": "Foo",
"tags": "bar,baz",
"timeout": 3600,
"grace": 60
})
self.assertEqual(r.status_code, 200)
doc = r.json()
assert "ping_url" in doc
self.assertEqual(doc["name"], "Foo")
self.assertEqual(doc["tags"], "bar,baz")
self.assertEqual(doc["last_ping"], None)
self.assertEqual(doc["n_pings"], 0)
self.assertTrue("schedule" not in doc)
self.assertTrue("tz" not in doc)
self.assertEqual(Check.objects.count(), 1)
self.check.refresh_from_db()
self.assertEqual(self.check.name, "Foo")
self.assertEqual(self.check.tags, "bar,baz")
self.assertEqual(self.check.timeout.total_seconds(), 3600)
self.assertEqual(self.check.grace.total_seconds(), 60)
def test_it_unassigns_channels(self):
channel = Channel(user=self.alice)
channel.save()
self.check.assign_all_channels()
r = self.post(self.check.code, {
"api_key": "abc",
"channels": ""
})
self.assertEqual(r.status_code, 200)
check = Check.objects.get()
self.assertEqual(check.channel_set.count(), 0)
def test_it_requires_post(self):
url = "/api/v1/checks/%s" % self.check.code
r = self.client.get(url, HTTP_X_API_KEY="abc")
self.assertEqual(r.status_code, 405)
def test_it_handles_invalid_uuid(self):
r = self.post("not-an-uuid", {"api_key": "abc"})
self.assertEqual(r.status_code, 400)
def test_it_handles_missing_check(self):
made_up_code = "07c2f548-9850-4b27-af5d-6c9dc157ec02"
r = self.post(made_up_code, {"api_key": "abc"})
self.assertEqual(r.status_code, 400)

+ 1
- 0
hc/api/urls.py View File

@ -6,6 +6,7 @@ urlpatterns = [
url(r'^ping/([\w-]+)/$', views.ping, name="hc-ping-slash"),
url(r'^ping/([\w-]+)$', views.ping, name="hc-ping"),
url(r'^api/v1/checks/$', views.checks),
url(r'^api/v1/checks/([\w-]+)$', views.update, name="hc-api-update"),
url(r'^api/v1/checks/([\w-]+)/pause$', views.pause, name="hc-api-pause"),
url(r'^badge/([\w-]+)/([\w-]{8})/([\w-]+).svg$', views.badge, name="hc-badge"),
]

+ 57
- 25
hc/api/views.py View File

@ -46,10 +46,30 @@ def ping(request, code):
return response
def _create_check(user, spec):
check = Check(user=user)
check.name = spec.get("name", "")
check.tags = spec.get("tags", "")
def _lookup(user, spec):
unique_fields = spec.get("unique", [])
if unique_fields:
existing_checks = Check.objects.filter(user=user)
if "name" in unique_fields:
existing_checks = existing_checks.filter(name=spec.get("name"))
if "tags" in unique_fields:
existing_checks = existing_checks.filter(tags=spec.get("tags"))
if "timeout" in unique_fields:
timeout = td(seconds=spec["timeout"])
existing_checks = existing_checks.filter(timeout=timeout)
if "grace" in unique_fields:
grace = td(seconds=spec["grace"])
existing_checks = existing_checks.filter(grace=grace)
return existing_checks.first()
def _update(check, spec):
if "name" in spec:
check.name = spec["name"]
if "tags" in spec:
check.tags = spec["tags"]
if "timeout" in spec and "schedule" not in spec:
check.timeout = td(seconds=spec["timeout"])
@ -63,31 +83,17 @@ def _create_check(user, spec):
if "tz" in spec and "schedule" in spec:
check.tz = spec["tz"]
unique_fields = spec.get("unique", [])
if unique_fields:
existing_checks = Check.objects.filter(user=user)
if "name" in unique_fields:
existing_checks = existing_checks.filter(name=check.name)
if "tags" in unique_fields:
existing_checks = existing_checks.filter(tags=check.tags)
if "timeout" in unique_fields:
existing_checks = existing_checks.filter(timeout=check.timeout)
if "grace" in unique_fields:
existing_checks = existing_checks.filter(grace=check.grace)
if existing_checks.count() > 0:
# There might be more than one matching check, return first
first_match = existing_checks.first()
return JsonResponse(first_match.to_dict(), status=200)
check.save()
# This needs to be done after saving the check, because of
# the M2M relation between checks and channels:
if spec.get("channels") == "*":
check.assign_all_channels()
if "channels" in spec:
if spec["channels"] == "*":
check.assign_all_channels()
elif spec["channels"] == "":
check.channel_set.clear()
return JsonResponse(check.to_dict(), status=201)
return check
@csrf_exempt
@ -100,13 +106,39 @@ def checks(request):
return JsonResponse(doc)
elif request.method == "POST":
return _create_check(request.user, request.json)
created = False
check = _lookup(request.user, request.json)
if check is None:
check = Check(user=request.user)
created = True
_update(check, request.json)
return JsonResponse(check.to_dict(), status=201 if created else 200)
# If request is neither GET nor POST, return "405 Method not allowed"
return HttpResponse(status=405)
@csrf_exempt
@uuid_or_400
@check_api_key
@validate_json(schemas.check)
def update(request, code):
if request.method != "POST":
return HttpResponse(status=405) # method not allowed
try:
check = Check.objects.get(code=code, user=request.user)
except Check.DoesNotExist:
return HttpResponseBadRequest()
_update(check, request.json)
return JsonResponse(check.to_dict(), status=200)
@csrf_exempt
@uuid_or_400
@check_api_key
def pause(request, code):
if request.method != "POST":


+ 2
- 0
hc/front/management/commands/pygmentize.py View File

@ -42,6 +42,8 @@ class Command(BaseCommand):
_process("list_checks_response", lexers.JsonLexer())
_process("create_check_request_a", lexers.BashLexer())
_process("create_check_request_b", lexers.BashLexer())
_process("update_check_request_a", lexers.BashLexer())
_process("update_check_request_b", lexers.BashLexer())
_process("create_check_response", lexers.JsonLexer())
_process("pause_check_request", lexers.BashLexer())
_process("pause_check_response", lexers.JsonLexer())

+ 105
- 1
templates/front/docs_api.html View File

@ -12,6 +12,7 @@ This is early days for healtchecks.io REST API. For now, there's API calls to:
<ul>
<li><a href="#list-checks">List existing checks</a></li>
<li><a href="#create-check">Create a new check</a></li>
<li><a href="#update-check">Update an existing check</a></li>
<li><a href="#pause-check">Pause monitoring of a check</a></li>
</ul>
@ -202,9 +203,112 @@ To create a "cron" check, specify the "schedule" and "tz" parameters.
<h3 class="api-section">Example Response</h3>
{% include "front/snippets/create_check_response.html" %}
<!-- ********************************************************************** /-->
<a class="section" name="update-check">
<h2 class="rule">Update an existing check</h2>
</a>
<div class="api-path">POST {{ SITE_ROOT }}/api/v1/checks/&lt;code&gt;</div>
<strong></strong>
<p>
Updates an existing check. All request parameters are optional. The
check is updated only with the supplied request parameters.
If any parameter is omitted, its value is left unchanged.
</p>
<h3 class="api-section">Request Parameters</h3>
<table class="table">
<tr>
<th>name</th>
<td>
<p>string, optional.</p>
<p>Name for the check.</p>
</td>
</tr>
<tr>
<th>tags</th>
<td>
<p>string, optional.</p>
<p>A space-delimited list of tags for the check.</p>
<p>Example:</p>
<pre>{"tags": "reports staging"}</pre>
</td>
</tr>
<tr>
<th>timeout</th>
<td>
<p>number, optional.</p>
<p>A number of seconds, the expected period of this check.</p>
<p>Minimum: 60 (one minute), maximum: 604800 (one week).</p>
<p>Example for 5 minute timeout:</p>
<pre>{"kind": "simple", "timeout": 300}</pre>
</td>
</tr>
<tr>
<th>grace</th>
<td>
<p>number, optional.</p>
<p>A number of seconds, the grace period for this check.</p>
<p>Minimum: 60 (one minute), maximum: 604800 (one week).</p>
</td>
</tr>
<tr>
<th>schedule</th>
<td>
<p>string, optional.</p>
<p>A cron expression defining this check's schedule.</p>
<p>If you specify both "timeout" and "schedule" parameters,
"timeout" will be ignored and "schedule" will be used.</p>
<p>Example for a check running every half-hour:</p>
<pre>{"schedule": "0,30 * * * *"}</pre>
</td>
</tr>
<tr>
<th>tz</th>
<td>
<p>string, optional.</p>
<p>Server's timezone. This setting only has effect in combination
with the "schedule" paremeter.</p>
<p>Example:</p>
<pre>{"tz": "Europe/Riga"}</pre>
</td>
</tr>
<tr>
<th>channels</th>
<td>
<p>string, optional.</p>
<p>Set this field to a special value "*"
to automatically assign all existing notification channels.
</p>
<p>Set this field to a special value "" (empty string)
to automatically <em>unassign</em> all notification channels.
</p>
</td>
</tr>
</table>
<h3 class="api-section">Response Codes</h3>
<table class="table">
<tr>
<th>200 OK</th>
<td>Returned if the check was successfully updated.</td>
</tr>
</table>
<h3 class="api-section">Example Request</h3>
{% include "front/snippets/update_check_request_a.html" %}
<br>
<p>Or, alternatively:</p>
{% include "front/snippets/update_check_request_b.html" %}
<h3 class="api-section">Example Response</h3>
{% include "front/snippets/create_check_response.html" %}
<!-- ********************************************************************** /-->
<a class="section" name="pause-check">
<h2 class="rule">Pause Monitoring of a Check</h2>


+ 2
- 1
templates/front/snippets/create_check_response.html View File

@ -8,6 +8,7 @@
<span class="nt">&quot;ping_url&quot;</span><span class="p">:</span> <span class="s2">&quot;{{ PING_ENDPOINT }}f618072a-7bde-4eee-af63-71a77c5723bc&quot;</span><span class="p">,</span>
<span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;new&quot;</span><span class="p">,</span>
<span class="nt">&quot;tags&quot;</span><span class="p">:</span> <span class="s2">&quot;prod www&quot;</span><span class="p">,</span>
<span class="nt">&quot;timeout&quot;</span><span class="p">:</span> <span class="mi">3600</span>
<span class="nt">&quot;timeout&quot;</span><span class="p">:</span> <span class="mi">3600</span><span class="p">,</span>
<span class="nt">&quot;update_url&quot;</span><span class="p">:</span> <span class="s2">&quot;{{ SITE_ROOT }}/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc&quot;</span><span class="p">,</span>
<span class="p">}</span>
</pre></div>

+ 2
- 1
templates/front/snippets/create_check_response.txt View File

@ -8,5 +8,6 @@
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "new",
"tags": "prod www",
"timeout": 3600
"timeout": 3600,
"update_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc",
}

+ 4
- 2
templates/front/snippets/list_checks_response.html View File

@ -10,7 +10,8 @@
<span class="nt">&quot;tags&quot;</span><span class="p">:</span> <span class="s2">&quot;foo&quot;</span><span class="p">,</span>
<span class="nt">&quot;pause_url&quot;</span><span class="p">:</span> <span class="s2">&quot;{{ SITE_ROOT }}/api/v1/checks/662ebe36-ecab-48db-afe3-e20029cb71e6/pause&quot;</span><span class="p">,</span>
<span class="nt">&quot;timeout&quot;</span><span class="p">:</span> <span class="mi">3600</span><span class="p">,</span>
<span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;up&quot;</span>
<span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;up&quot;</span><span class="p">,</span>
<span class="nt">&quot;update_url&quot;</span><span class="p">:</span> <span class="s2">&quot;{{ SITE_ROOT }}/api/v1/checks/662ebe36-ecab-48db-afe3-e20029cb71e6&quot;</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;last_ping&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
@ -23,7 +24,8 @@
<span class="nt">&quot;pause_url&quot;</span><span class="p">:</span> <span class="s2">&quot;{{ SITE_ROOT }}/api/v1/checks/9d17c61f-5c4f-4cab-b517-11e6b2679ced/pause&quot;</span><span class="p">,</span>
<span class="nt">&quot;tz&quot;</span><span class="p">:</span> <span class="s2">&quot;UTC&quot;</span><span class="p">,</span>
<span class="nt">&quot;schedule&quot;</span><span class="p">:</span> <span class="s2">&quot;0/10 * * * *&quot;</span><span class="p">,</span>
<span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;new&quot;</span>
<span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;new&quot;</span><span class="p">,</span>
<span class="nt">&quot;update_url&quot;</span><span class="p">:</span> <span class="s2">&quot;{{ SITE_ROOT }}/api/v1/checks/9d17c61f-5c4f-4cab-b517-11e6b2679ced&quot;</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>


+ 4
- 2
templates/front/snippets/list_checks_response.txt View File

@ -10,7 +10,8 @@
"tags": "foo",
"pause_url": "SITE_ROOT/api/v1/checks/662ebe36-ecab-48db-afe3-e20029cb71e6/pause",
"timeout": 3600,
"status": "up"
"status": "up",
"update_url": "SITE_ROOT/api/v1/checks/662ebe36-ecab-48db-afe3-e20029cb71e6"
},
{
"last_ping": null,
@ -23,7 +24,8 @@
"pause_url": "SITE_ROOT/api/v1/checks/9d17c61f-5c4f-4cab-b517-11e6b2679ced/pause",
"tz": "UTC",
"schedule": "0/10 * * * *",
"status": "new"
"status": "new",
"update_url": "SITE_ROOT/api/v1/checks/9d17c61f-5c4f-4cab-b517-11e6b2679ced"
}
]
}

+ 2
- 1
templates/front/snippets/pause_check_response.html View File

@ -8,6 +8,7 @@
<span class="nt">&quot;ping_url&quot;</span><span class="p">:</span> <span class="s2">&quot;{{ PING_ENDPOINT }}f618072a-7bde-4eee-af63-71a77c5723bc&quot;</span><span class="p">,</span>
<span class="nt">&quot;status&quot;</span><span class="p">:</span> <span class="s2">&quot;paused&quot;</span><span class="p">,</span>
<span class="nt">&quot;tags&quot;</span><span class="p">:</span> <span class="s2">&quot;prod www&quot;</span><span class="p">,</span>
<span class="nt">&quot;timeout&quot;</span><span class="p">:</span> <span class="mi">3600</span>
<span class="nt">&quot;timeout&quot;</span><span class="p">:</span> <span class="mi">3600</span><span class="p">,</span>
<span class="nt">&quot;update_url&quot;</span><span class="p">:</span> <span class="s2">&quot;{{ SITE_ROOT }}/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc&quot;</span>
<span class="p">}</span>
</pre></div>

+ 2
- 1
templates/front/snippets/pause_check_response.txt View File

@ -8,5 +8,6 @@
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "paused",
"tags": "prod www",
"timeout": 3600
"timeout": 3600,
"update_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc"
}

+ 4
- 0
templates/front/snippets/update_check_request_a.html View File

@ -0,0 +1,4 @@
<div class="highlight"><pre><span></span>curl {{ SITE_ROOT }}/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc <span class="se">\</span>
--header <span class="s2">&quot;X-Api-Key: your-api-key&quot;</span> <span class="se">\</span>
--data <span class="s1">&#39;{&quot;name&quot;: &quot;Backups&quot;, &quot;tags&quot;: &quot;prod www&quot;, &quot;timeout&quot;: 3600, &quot;grace&quot;: 60}&#39;</span>
</pre></div>

+ 3
- 0
templates/front/snippets/update_check_request_a.txt View File

@ -0,0 +1,3 @@
curl SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc \
--header "X-Api-Key: your-api-key" \
--data '{"name": "Backups", "tags": "prod www", "timeout": 3600, "grace": 60}'

+ 3
- 0
templates/front/snippets/update_check_request_b.html View File

@ -0,0 +1,3 @@
<div class="highlight"><pre><span></span>curl {{ SITE_ROOT }}/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc <span class="se">\</span>
--data <span class="s1">&#39;{&quot;api_key&quot;: &quot;your-api-key&quot;, &quot;name&quot;: &quot;Backups&quot;, &quot;tags&quot;: &quot;prod www&quot;, &quot;timeout&quot;: 3600, &quot;grace&quot;: 60}&#39;</span>
</pre></div>

+ 2
- 0
templates/front/snippets/update_check_request_b.txt View File

@ -0,0 +1,2 @@
curl SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc \
--data '{"api_key": "your-api-key", "name": "Backups", "tags": "prod www", "timeout": 3600, "grace": 60}'

Loading…
Cancel
Save