Skip to content

Commit 952249d

Browse files
committed
Add infrastructure for pagerduty integration
Right our pagerduty notifications come from heroku email alerts and a pingdom integration. We want to start paging on more specific metrics. So far we've discussed: - When no crate is uploaded for N period of time (likely 4 hours) - When a background job has failed N times (likely 5) - When the background queue has N jobs (likely 5) In order to do this we need to have some code in place that actually starts an incident and pages whoever is on call. We'd also like to test this code before we start relying on it, so I've added a binary we can use to make sure everything is configured correctly.
1 parent 7920080 commit 952249d

File tree

4 files changed

+157
-29
lines changed

4 files changed

+157
-29
lines changed

Cargo.lock

Lines changed: 30 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bin/test-pagerduty.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! Send a test event to pagerduty
2+
//!
3+
//! Usage:
4+
//! cargo run --bin test-pagerduty event_type [description]
5+
//!
6+
//! Event type can be trigger, acknowledge, or resolve
7+
8+
#![deny(warnings)]
9+
10+
extern crate cargo_registry;
11+
12+
use std::env::args;
13+
14+
use cargo_registry::on_call;
15+
16+
fn main() {
17+
let args = args().collect::<Vec<_>>();
18+
19+
let event_type = &*args[1];
20+
let description = args.get(2).cloned();
21+
22+
let event = match event_type {
23+
"trigger" => on_call::Event::Trigger {
24+
incident_key: Some("test".into()),
25+
description: description.unwrap_or_else(|| "Test event".into()),
26+
},
27+
"acknowledge" => on_call::Event::Acknowledge {
28+
incident_key: "test".into(),
29+
description,
30+
},
31+
"resolve" => on_call::Event::Resolve {
32+
incident_key: "test".into(),
33+
description,
34+
},
35+
_ => panic!("Event type must be trigger, acknowledge, or resolve"),
36+
};
37+
event.send().unwrap()
38+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub mod email;
6969
pub mod git;
7070
pub mod github;
7171
pub mod middleware;
72+
pub mod on_call;
7273
pub mod render;
7374
pub mod schema;
7475
pub mod uploaders;

src/on_call.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use curl::easy::*;
2+
use serde_json;
3+
use std::env;
4+
use std::io::prelude::*;
5+
6+
use util::*;
7+
8+
#[derive(Serialize, Debug)]
9+
#[serde(rename_all = "snake_case", tag = "event_type")]
10+
pub enum Event {
11+
Trigger {
12+
incident_key: Option<String>,
13+
description: String,
14+
},
15+
Acknowledge {
16+
incident_key: String,
17+
description: Option<String>,
18+
},
19+
Resolve {
20+
incident_key: String,
21+
description: Option<String>,
22+
},
23+
}
24+
25+
impl Event {
26+
/// Sends the event to pagerduty.
27+
///
28+
/// If the variant is `Trigger`, this will page whoever is on call
29+
/// (potentially waking them up at 3 AM).
30+
pub fn send(self) -> CargoResult<()> {
31+
let api_token = env::var("PAGERDUTY_API_TOKEN")?;
32+
let service_key = env::var("PAGERDUTY_INTEGRATION_KEY")?;
33+
34+
let mut headers = List::new();
35+
headers.append("Accept: application/vnd.pagerduty+json;version=2")?;
36+
headers.append(&format!("Authorization: Token token={}", api_token))?;
37+
headers.append("Content-Type: application/json")?;
38+
39+
let mut handle = Easy::new();
40+
handle.url("https://events.pagerduty.com/generic/2010-04-15/create_event.json")?;
41+
handle.post(true)?;
42+
handle.http_headers(headers)?;
43+
44+
let full_event = FullEvent { service_key, event: self };
45+
let json_body = serde_json::to_string(&full_event)?;
46+
let mut bytes_to_write = json_body.as_bytes();
47+
let mut data = Vec::new();
48+
49+
{
50+
let mut handle = handle.transfer();
51+
handle.read_function(|bytes| {
52+
bytes_to_write.read(bytes).map_err(|_| ReadError::Abort)
53+
})?;
54+
handle.write_function(|buf| {
55+
data.extend_from_slice(buf);
56+
Ok(buf.len())
57+
})?;
58+
handle.perform()?;
59+
}
60+
61+
match handle.response_code()? {
62+
200 => Ok(()),
63+
400 => {
64+
let error = serde_json::from_slice::<InvalidEvent>(&data)?;
65+
Err(internal(&format_args!("pagerduty error: {:?}", error)))
66+
},
67+
403 => Err(internal("rate limited by pagerduty")),
68+
n => {
69+
let resp = String::from_utf8_lossy(&data);
70+
Err(internal(&format_args!(
71+
"Got a non 200 response code from pagerduty: {} with {}", n, resp)))
72+
}
73+
}
74+
}
75+
}
76+
77+
#[derive(Serialize, Debug)]
78+
struct FullEvent {
79+
service_key: String,
80+
#[serde(flatten)]
81+
event: Event,
82+
}
83+
84+
#[derive(Deserialize, Debug)]
85+
struct InvalidEvent {
86+
message: String,
87+
errors: Vec<String>,
88+
}

0 commit comments

Comments
 (0)