New feature: pause monitoring of an individual check. Fixes #67

This commit is contained in:
Pēteris Caune 2016-08-01 21:57:11 +03:00
parent 63b10b40ce
commit 51cda31449
26 changed files with 283 additions and 60 deletions

View File

@ -81,7 +81,7 @@ class Check(models.Model):
return errors
def get_status(self):
if self.status == "new":
if self.status in ("new", "paused"):
return self.status
now = timezone.now()
@ -108,9 +108,12 @@ class Check(models.Model):
return [t.strip() for t in self.tags.split(" ") if t.strip()]
def to_dict(self):
pause_rel_url = reverse("hc-api-pause", args=[self.code])
result = {
"name": self.name,
"ping_url": self.url(),
"pause_url": settings.SITE_ROOT + pause_rel_url,
"tags": self.tags,
"timeout": int(self.timeout.total_seconds()),
"grace": int(self.grace.total_seconds()),

View File

@ -47,6 +47,8 @@ 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 = "http://localhost:8000/api/v1/checks/%s/pause" % self.a1.code
self.assertEqual(checks["Alice 1"]["pause_url"], pause_url)
next_ping = self.now + td(seconds=3600)
self.assertEqual(checks["Alice 1"]["next_ping"], next_ping.isoformat())

View File

@ -0,0 +1,34 @@
from hc.api.models import Check
from hc.test import BaseTestCase
class PauseTestCase(BaseTestCase):
def test_it_works(self):
check = Check(user=self.alice, status="up")
check.save()
url = "/api/v1/checks/%s/pause" % check.code
r = self.client.post(url, "", content_type="application/json",
HTTP_X_API_KEY="abc")
self.assertEqual(r.status_code, 200)
check.refresh_from_db()
self.assertEqual(check.status, "paused")
def test_it_only_allows_post(self):
url = "/api/v1/checks/1659718b-21ad-4ed1-8740-43afc6c41524/pause"
r = self.client.get(url, HTTP_X_API_KEY="abc")
self.assertEqual(r.status_code, 405)
def test_it_validates_ownership(self):
check = Check(user=self.bob, status="up")
check.save()
url = "/api/v1/checks/%s/pause" % check.code
r = self.client.post(url, "", content_type="application/json",
HTTP_X_API_KEY="abc")
self.assertEqual(r.status_code, 400)

View File

@ -13,12 +13,22 @@ class PingTestCase(TestCase):
r = self.client.get("/ping/%s/" % self.check.code)
assert r.status_code == 200
same_check = Check.objects.get(code=self.check.code)
assert same_check.status == "up"
self.check.refresh_from_db()
assert self.check.status == "up"
ping = Ping.objects.latest("id")
assert ping.scheme == "http"
def test_it_changes_status_of_paused_check(self):
self.check.status = "paused"
self.check.save()
r = self.client.get("/ping/%s/" % self.check.code)
assert r.status_code == 200
self.check.refresh_from_db()
assert self.check.status == "up"
def test_post_works(self):
csrf_client = Client(enforce_csrf_checks=True)
r = csrf_client.post("/ping/%s/" % self.check.code)

View File

@ -6,5 +6,6 @@ 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-]+)/pause$', views.pause, name="hc-api-pause"),
url(r'^badge/([\w-]+)/([\w-]{8})/([\w-]+).svg$', views.badge, name="hc-badge"),
]

View File

@ -23,7 +23,7 @@ def ping(request, code):
check.n_pings = F("n_pings") + 1
check.last_ping = timezone.now()
if check.status == "new":
if check.status in ("new", "paused"):
check.status = "up"
check.save()
@ -76,6 +76,23 @@ def checks(request):
return HttpResponse(status=405)
@csrf_exempt
@check_api_key
def pause(request, code):
if request.method != "POST":
# Method not allowed
return HttpResponse(status=405)
try:
check = Check.objects.get(code=code, user=request.user)
except Check.DoesNotExist:
return HttpResponseBadRequest()
check.status = "paused"
check.save()
return JsonResponse(check.to_dict())
@never_cache
def badge(request, username, signature, tag):
if not check_signature(username, tag, signature):

View File

@ -40,3 +40,5 @@ class Command(BaseCommand):
_process("list_checks_response", lexers.JsonLexer())
_process("create_check_request", lexers.BashLexer())
_process("create_check_response", lexers.JsonLexer())
_process("pause_check_request", lexers.BashLexer())
_process("pause_check_response", lexers.JsonLexer())

View File

@ -0,0 +1,20 @@
from hc.api.models import Check
from hc.test import BaseTestCase
class PauseTestCase(BaseTestCase):
def setUp(self):
super(PauseTestCase, self).setUp()
self.check = Check(user=self.alice, status="up")
self.check.save()
def test_it_pauses(self):
url = "/checks/%s/pause/" % self.check.code
self.client.login(username="alice@example.org", password="password")
r = self.client.post(url)
self.assertRedirects(r, "/checks/")
self.check.refresh_from_db()
self.assertEqual(self.check.status, "paused")

View File

@ -1,34 +1,43 @@
from django.conf.urls import url
from django.conf.urls import include, url
from hc.front import views
urlpatterns = [
url(r'^$', views.index, name="hc-index"),
url(r'^checks/$', views.my_checks, name="hc-checks"),
url(r'^checks/add/$', views.add_check, name="hc-add-check"),
url(r'^checks/([\w-]+)/name/$', views.update_name, name="hc-update-name"),
url(r'^checks/([\w-]+)/timeout/$', views.update_timeout, name="hc-update-timeout"),
url(r'^checks/([\w-]+)/remove/$', views.remove_check, name="hc-remove-check"),
url(r'^checks/([\w-]+)/log/$', views.log, name="hc-log"),
url(r'^docs/$', views.docs, name="hc-docs"),
url(r'^docs/api/$', views.docs_api, name="hc-docs-api"),
url(r'^about/$', views.about, name="hc-about"),
url(r'^privacy/$', views.privacy, name="hc-privacy"),
url(r'^terms/$', views.terms, name="hc-terms"),
url(r'^integrations/$', views.channels, name="hc-channels"),
url(r'^integrations/add/$', views.add_channel, name="hc-add-channel"),
url(r'^integrations/add_email/$', views.add_email, name="hc-add-email"),
url(r'^integrations/add_webhook/$', views.add_webhook, name="hc-add-webhook"),
url(r'^integrations/add_pd/$', views.add_pd, name="hc-add-pd"),
url(r'^integrations/add_slack/$', views.add_slack, name="hc-add-slack"),
url(r'^integrations/add_slack_btn/$', views.add_slack_btn, name="hc-add-slack-btn"),
url(r'^integrations/add_hipchat/$', views.add_hipchat, name="hc-add-hipchat"),
url(r'^integrations/add_pushbullet/$', views.add_pushbullet, name="hc-add-pushbullet"),
url(r'^integrations/add_pushover/$', views.add_pushover, name="hc-add-pushover"),
url(r'^integrations/add_victorops/$', views.add_victorops, name="hc-add-victorops"),
url(r'^integrations/([\w-]+)/checks/$', views.channel_checks, name="hc-channel-checks"),
url(r'^integrations/([\w-]+)/remove/$', views.remove_channel, name="hc-remove-channel"),
url(r'^integrations/([\w-]+)/verify/([\w-]+)/$',
views.verify_email, name="hc-verify-email"),
check_urls = [
url(r'^name/$', views.update_name, name="hc-update-name"),
url(r'^timeout/$', views.update_timeout, name="hc-update-timeout"),
url(r'^pause/$', views.pause, name="hc-pause"),
url(r'^remove/$', views.remove_check, name="hc-remove-check"),
url(r'^log/$', views.log, name="hc-log"),
]
channel_urls = [
url(r'^$', views.channels, name="hc-channels"),
url(r'^add/$', views.add_channel, name="hc-add-channel"),
url(r'^add_email/$', views.add_email, name="hc-add-email"),
url(r'^add_webhook/$', views.add_webhook, name="hc-add-webhook"),
url(r'^add_pd/$', views.add_pd, name="hc-add-pd"),
url(r'^add_slack/$', views.add_slack, name="hc-add-slack"),
url(r'^add_slack_btn/$', views.add_slack_btn, name="hc-add-slack-btn"),
url(r'^add_hipchat/$', views.add_hipchat, name="hc-add-hipchat"),
url(r'^add_pushbullet/$', views.add_pushbullet, name="hc-add-pushbullet"),
url(r'^add_pushover/$', views.add_pushover, name="hc-add-pushover"),
url(r'^add_victorops/$', views.add_victorops, name="hc-add-victorops"),
url(r'^([\w-]+)/checks/$', views.channel_checks, name="hc-channel-checks"),
url(r'^([\w-]+)/remove/$', views.remove_channel, name="hc-remove-channel"),
url(r'^([\w-]+)/verify/([\w-]+)/$', views.verify_email,
name="hc-verify-email"),
]
urlpatterns = [
url(r'^$', views.index, name="hc-index"),
url(r'^checks/$', views.my_checks, name="hc-checks"),
url(r'^checks/add/$', views.add_check, name="hc-add-check"),
url(r'^checks/([\w-]+)/', include(check_urls)),
url(r'^integrations/', include(channel_urls)),
url(r'^docs/$', views.docs, name="hc-docs"),
url(r'^docs/api/$', views.docs_api, name="hc-docs-api"),
url(r'^about/$', views.about, name="hc-about"),
url(r'^privacy/$', views.privacy, name="hc-privacy"),
url(r'^terms/$', views.terms, name="hc-terms"),
]

View File

@ -44,7 +44,7 @@ def my_checks(request):
if status == "down":
down_tags.add(tag)
elif check.in_grace_period():
elif check.in_grace_period() and status != "paused":
grace_tags.add(tag)
ctx = {
@ -169,6 +169,21 @@ def update_timeout(request, code):
return redirect("hc-checks")
@login_required
@uuid_or_400
def pause(request, code):
assert request.method == "POST"
check = get_object_or_404(Check, code=code)
if check.user_id != request.team.user.id:
return HttpResponseForbidden()
check.status = "paused"
check.save()
return redirect("hc-checks")
@login_required
@uuid_or_400
def remove_check(request, code):

View File

@ -2,9 +2,9 @@ from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^admin/', include(admin.site.urls)),
url(r'^accounts/', include('hc.accounts.urls')),
url(r'^', include('hc.api.urls')),
url(r'^', include('hc.front.urls')),
url(r'^', include('hc.payments.urls'))
url(r'^', include('hc.api.urls')),
url(r'^', include('hc.front.urls')),
url(r'^', include('hc.payments.urls'))
]

View File

@ -42,3 +42,12 @@ h3.api-section {
font-weight: bold;
margin-bottom: 1em;
}
a.section {
color: #000;
text-decoration: none;
}
a.section:hover {
text-decoration: none;
}

View File

@ -123,21 +123,6 @@ $(function () {
return false;
});
$("#show-urls").click(function() {
$("#show-urls").addClass("active");
$(".my-checks-url").removeClass("off");
$("#show-emails").removeClass("active");
$(".my-checks-email").addClass("off");
});
$("#show-emails").click(function() {
$("#show-urls").removeClass("active");
$(".my-checks-url").addClass("off");
$("#show-emails").addClass("active");
$(".my-checks-email").removeClass("off");
});
$("#my-checks-tags button").click(function() {
// .active has not been updated yet by bootstrap code,
@ -176,6 +161,13 @@ $(function () {
});
$(".pause-check").click(function(e) {
var url = e.target.getAttribute("data-url");
$("#pause-form").attr("action", url).submit();
return false;
});
$(".usage-examples").click(function(e) {
var a = e.target;
var url = a.getAttribute("data-url");

View File

@ -205,6 +205,15 @@ powershell.exe -ExecutionPolicy bypass -File C:\Scripts\healthchecks.ps1
A check that has been created, but has not received any pings yet.
</td>
</tr>
<tr>
<td>
<span class="glyphicon glyphicon-pause new"></span>
</td>
<td>
<strong>Monitoring Paused.</strong>
You can resume monitoring of a paused check by pinging it.
</td>
</tr>
<tr>
<td>
<span class="glyphicon glyphicon-ok-sign up"></span>

View File

@ -7,9 +7,13 @@
<h2>REST API</h2>
<p>
This is early days for healtchecks.io REST API. For now, there's just
one API resource for listing and creating checks.
This is early days for healtchecks.io REST API. For now, there's API calls to:
</p>
<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="#pause-check">Pause monitoring of a check</a></li>
</ul>
<h2 class="rule">Authentication</h2>
<p>Your requests to healtchecks.io REST API must authenticate using an
@ -47,7 +51,11 @@ and 5xx indicates a server error.
The response may contain a JSON document with additional data.
</p>
<h2 class="rule">List checks</h2>
<!-- ********************************************************************** /-->
<a class="section" name="list-checks">
<h2 class="rule">List checks</h2>
</a>
<div class="api-path">GET {{ SITE_ROOT }}/api/v1/checks/</div>
@ -61,7 +69,11 @@ The response may contain a JSON document with additional data.
<h3 class="api-section">Example Response</h3>
{% include "front/snippets/list_checks_response.html" %}
<!-- ********************************************************************** /-->
<a class="section" name="create-check">
<h2 class="rule">Create a check</h2>
</a>
<div class="api-path">POST {{ SITE_ROOT }}/api/v1/checks/</div>
@ -123,4 +135,32 @@ The response may contain a JSON document with additional data.
<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>
</a>
<div class="api-path">POST {{ SITE_ROOT }}/api/v1/checks/&lt;uuid&gt;/pause</div>
<strong></strong>
<p>
Disables monitoring for a check, without removing it. The check goes
into a "paused" state. You can resume monitoring of the check by pinging
it.
</p>
<p>
This API call has no request parameters.
</p>
<h3 class="api-section">Example Request</h3>
{% include "front/snippets/pause_check_request.html" %}
<h3 class="api-section">Example Response</h3>
{% include "front/snippets/pause_check_response.html" %}
{% endblock %}

View File

@ -274,6 +274,10 @@
</div>
</div>
<form id="pause-form" method="post">
{% csrf_token %}
</form>
{% endblock %}
{% block scripts %}

View File

@ -15,7 +15,11 @@
<tr class="checks-row">
<td class="indicator-cell">
{% if check.get_status == "new" %}
<span class="glyphicon glyphicon-question-sign new"></span>
<span class="glyphicon glyphicon-question-sign new"
data-toggle="tooltip" title="New. Has never received a ping."></span>
{% elif check.get_status == "paused" %}
<span class="glyphicon glyphicon-pause new"
data-toggle="tooltip" title="Monitoring paused. Ping to resume."></span>
{% elif check.in_grace_period %}
<span class="glyphicon glyphicon-exclamation-sign grace"></span>
{% elif check.get_status == "up" %}
@ -75,6 +79,14 @@
<span class="glyphicon glyphicon-cog" aria-hidden="true"></span>
</button>
<ul class="dropdown-menu">
<li {% if check.status == "new" or check.status == "paused" %}class="disabled"{% endif %}>
<a class="pause-check"
href="#"
data-url="{% url 'hc-pause' check.code %}">
Pause Monitoring
</a>
</li>
<li role="separator" class="divider"></li>
<li>
<a href="{% url 'hc-log' check.code %}">
Log

View File

@ -25,6 +25,8 @@
<td>
{% if check.get_status == "new" %}
<span class="label label-default">NEW</span>
{% elif check.get_status == "paused" %}
<span class="label label-default">PAUSED</span>
{% elif check.in_grace_period %}
<span class="label label-warning">LATE</span>
{% elif check.get_status == "up" %}

View File

@ -4,7 +4,9 @@
<span class="nt">&quot;n_pings&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="nt">&quot;name&quot;</span><span class="p">:</span> <span class="s2">&quot;Backups&quot;</span><span class="p">,</span>
<span class="nt">&quot;next_ping&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;ping_url&quot;</span><span class="p">:</span> <span class="s2">&quot;{{ PING_ENDPOINT }}0c8983c9-9d73-446f-adb5-0641fdacc9d4&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/f618072a-7bde-4eee-af63-71a77c5723bc/pause&quot;</span><span class="p">,</span>
<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="p">}</span>

View File

@ -4,7 +4,9 @@
"n_pings": 0,
"name": "Backups",
"next_ping": null,
"ping_url": "PING_ENDPOINT0c8983c9-9d73-446f-adb5-0641fdacc9d4",
"pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "new",
"tags": "prod www",
"timeout": 3600
}

View File

@ -6,7 +6,9 @@
<span class="nt">&quot;n_pings&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;name&quot;</span><span class="p">:</span> <span class="s2">&quot;Api test 1&quot;</span><span class="p">,</span>
<span class="nt">&quot;next_ping&quot;</span><span class="p">:</span> <span class="s2">&quot;2016-07-09T14:58:43.366568+00:00&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/25c55e7c-8092-4d21-ad06-7dacfbb6fc10/pause&quot;</span><span class="p">,</span>
<span class="nt">&quot;ping_url&quot;</span><span class="p">:</span> <span class="s2">&quot;{{ PING_ENDPOINT }}25c55e7c-8092-4d21-ad06-7dacfbb6fc10&quot;</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="p">,</span>
<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;timeout&quot;</span><span class="p">:</span> <span class="mi">3600</span>
<span class="p">},</span>
@ -16,7 +18,9 @@
<span class="nt">&quot;n_pings&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="nt">&quot;name&quot;</span><span class="p">:</span> <span class="s2">&quot;Api test 2&quot;</span><span class="p">,</span>
<span class="nt">&quot;next_ping&quot;</span><span class="p">:</span> <span class="kc">null</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/7e1b6e61-b16f-4671-bae3-e3233edd1b5e/pause&quot;</span><span class="p">,</span>
<span class="nt">&quot;ping_url&quot;</span><span class="p">:</span> <span class="s2">&quot;{{ PING_ENDPOINT }}7e1b6e61-b16f-4671-bae3-e3233edd1b5e&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;bar baz&quot;</span><span class="p">,</span>
<span class="nt">&quot;timeout&quot;</span><span class="p">:</span> <span class="mi">60</span>
<span class="p">}</span>

View File

@ -6,7 +6,9 @@
"n_pings": 1,
"name": "Api test 1",
"next_ping": "2016-07-09T14:58:43.366568+00:00",
"pause_url": "SITE_ROOT/api/v1/checks/25c55e7c-8092-4d21-ad06-7dacfbb6fc10/pause",
"ping_url": "PING_ENDPOINT25c55e7c-8092-4d21-ad06-7dacfbb6fc10",
"status": "up",
"tags": "foo",
"timeout": 3600
},
@ -16,7 +18,9 @@
"n_pings": 0,
"name": "Api test 2",
"next_ping": null,
"pause_url": "SITE_ROOT/api/v1/checks/7e1b6e61-b16f-4671-bae3-e3233edd1b5e/pause",
"ping_url": "PING_ENDPOINT7e1b6e61-b16f-4671-bae3-e3233edd1b5e",
"status": "new",
"tags": "bar baz",
"timeout": 60
}

View File

@ -0,0 +1,3 @@
<div class="highlight"><pre><span></span>curl {{ SITE_ROOT }}/api/v1/checks/0c8983c9-9d73-446f-adb5-0641fdacc9d4/pause <span class="se">\</span>
--request POST --header <span class="s2">&quot;X-Api-Key: your-api-key&quot;</span>
</pre></div>

View File

@ -0,0 +1,2 @@
curl SITE_ROOT/api/v1/checks/0c8983c9-9d73-446f-adb5-0641fdacc9d4/pause \
--request POST --header "X-Api-Key: your-api-key"

View File

@ -0,0 +1,13 @@
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="nt">&quot;grace&quot;</span><span class="p">:</span> <span class="mi">60</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>
<span class="nt">&quot;n_pings&quot;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="nt">&quot;name&quot;</span><span class="p">:</span> <span class="s2">&quot;Backups&quot;</span><span class="p">,</span>
<span class="nt">&quot;next_ping&quot;</span><span class="p">:</span> <span class="kc">null</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/f618072a-7bde-4eee-af63-71a77c5723bc/pause&quot;</span><span class="p">,</span>
<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="p">}</span>
</pre></div>

View File

@ -0,0 +1,12 @@
{
"grace": 60,
"last_ping": null,
"n_pings": 0,
"name": "Backups",
"next_ping": null,
"pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause",
"ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc",
"status": "paused",
"tags": "prod www",
"timeout": 3600
}