Cloud
banshee/cloud-collection/cloud/aws/https/
cloudwatch-alarms
The CloudWatch alarms of an AWS region in the state ALARM, how many and their names, by the CloudWatch API.
by banshee0 installations1 version
Published on October 27, 2026. First version.
// The CloudWatch alarms of an AWS region that are in the state ALARM, by the CloudWatch API with an access key:
// how many, and their names. The alarms are those the account defines in CloudWatch; this collector reads their
// state, it does not judge the metrics again.
// Reads GET https://monitoring.<region>.amazonaws.com/?Action=DescribeAlarms&StateValue=ALARM, page after page,
// signed by Signature Version 4.
need('protocol:http');
need('protocol:aws');
$region = variable('aws:region', 'text', array(
'label' => 'Region',
'help' => 'The AWS region of the alarms, as AWS writes it in an API: eu-west-3, us-east-1.',
));
$prefix = variable('aws:alarm-prefix', 'text', array(
'label' => 'Alarm name prefix',
'help' => 'Only the alarms whose name starts with this text, for instance prod-; empty, every alarm of the region.',
'required' => false,
));
output('alarms', 'number', array('label' => 'Alarms in ALARM'));
output('alarm-names', 'text', array('label' => 'Names of the alarms in ALARM'));
watch.above('alarm', 'CloudWatch alarm raised', 'alarms', array('critical' => 0));
// One signed GET on an AWS API of the Query protocol (EC2, RDS, CloudWatch): the parameters in the address, the
// answer in XML. aws.sign gives the headers that carry the signature: the keys never leave. A refusal is told
// by the error code AWS writes in its answer; a throttled request or a fault of AWS is not one.
function aws_get($service, $region, $query, $what) {
$url = 'https://' . $service . '.' . $region . '.amazonaws.com/?' . url.query($query);
$answer = http.get($url, headers=aws.sign('GET', $url, array(), '', $region, $service));
if ($answer['status'] != 200) {
$code = '';
$found = text.match($answer['body'], '<Code>([^<]+)</Code>');
if (list.count($found) > 0) {
$code = $found[1];
}
if ($code == 'Throttling' || $code == 'RequestLimitExceeded' || $answer['status'] >= 500) {
fail('unreachable', 'AWS answers the reading of ' . $what . ' with code ' . $answer['status'] . ' ' . $code);
}
if ($answer['status'] == 400 || $answer['status'] == 401 || $answer['status'] == 403) {
fail('misconfigured', 'AWS refuses to read ' . $what . ', code ' . $answer['status'] . ' ' . $code,
'Check the access key, the permissions of its user or role, and the region.');
}
fail('unreachable', 'AWS answers the reading of ' . $what . ' with code ' . $answer['status']);
}
return xml.read($answer['body']);
}
// The first child of that name, array() when it is absent or empty. An empty element, such as a list without
// members written <name/>, reads as the text '' and not as an array.
function element($parent, $name) {
$children = list.get($parent, $name, array());
if (list.count($children) == 0 || !list.is($children[0])) {
return array();
}
return $children[0];
}
// The text of the first child of that name, '' when it is absent.
function child_text($parent, $name) {
$children = list.get($parent, $name, array());
if (list.count($children) == 0) {
return '';
}
return $children[0];
}
// The region goes into the name of the host: it must be a name.
if (list.count(text.match($region, '^[a-z0-9-]+$')) == 0) {
fail('misconfigured', 'The region holds other characters than lower-case letters, digits and hyphens');
}
// <DescribeAlarmsResponse><DescribeAlarmsResult><MetricAlarms><member><AlarmName>cpu-high</AlarmName>
// <StateValue>ALARM</StateValue>…</member></MetricAlarms><CompositeAlarms/><LogAlarms/><NextToken>…</NextToken>
// </DescribeAlarmsResult><ResponseMetadata>…</ResponseMetadata></DescribeAlarmsResponse>. Without AlarmTypes, AWS
// lists the metric alarms only: the three kinds are asked for.
$names = array();
$token = '';
while (true) {
$query = array('Action' => 'DescribeAlarms', 'Version' => '2010-08-01', 'StateValue' => 'ALARM', 'MaxRecords' => 100,
'AlarmTypes.member.1' => 'MetricAlarm', 'AlarmTypes.member.2' => 'CompositeAlarm', 'AlarmTypes.member.3' => 'LogAlarm');
if ($prefix != '') {
$query['AlarmNamePrefix'] = $prefix;
}
if ($token != '') {
$query['NextToken'] = $token;
}
$result = element(element(aws_get('monitoring', $region, $query, 'the CloudWatch alarms'), 'DescribeAlarmsResponse'),
'DescribeAlarmsResult');
foreach (array('MetricAlarms', 'CompositeAlarms', 'LogAlarms') as $kind) {
foreach (list.get(element($result, $kind), 'member', array()) as $alarm) {
$names = list.add($names, child_text($alarm, 'AlarmName'));
}
}
$token = child_text($result, 'NextToken');
if ($token == '') {
break;
}
}
value('alarms', list.count($names));
value('alarm-names', text.join($names, ' | '));