Skip to content
This repository has been archived by the owner on Nov 19, 2023. It is now read-only.

Commit

Permalink
Create Freshdesk tickets for expiring domains
Browse files Browse the repository at this point in the history
  • Loading branch information
Indy Griffiths committed Jul 6, 2018
1 parent 5d90b3a commit 7d7b8e0
Show file tree
Hide file tree
Showing 13 changed files with 429 additions and 4 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ CLOUDFLARE_USER_EMAIL=foo@silverstripe.com
```


### Freshdesk Alerting

When defined, upcoming certificate renewals will be created as Freshdesk tickets. As the certificate approaches expiration, the priority will be increased. If a new certificate is detected, the ticket will be closed.

```
FRESHDESK_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
FRESHDESK_USER_ID=123456789
FRESHDESK_DOMAIN=https://foo.freshdesk.com
```

Additional options can be configured in the CMS, such as the group and product the ticket should be created as.

### OpsGenie Alerting

When defined, any upcoming certificate renewals will be created as alerts in OpsGenie if configured in the Settings section of the CMS.
Expand Down
3 changes: 2 additions & 1 deletion mysite/_config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ SiteConfig:
extensions:
- OpsGenieSiteConfigExtension
- SlackSiteConfigExtension
- FreshdeskSiteConfigExtension
Domain:
api_access: true
Certificate:
api_access: true
RESTfulAPI:
embedded_records:
Domain:
- Certificates
- Certificates
29 changes: 29 additions & 0 deletions mysite/code/Extensions/FreshdeskSiteConfigExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/**
* Class for managing Freshdesk settings around alerting for expiring certificates.
*/
class FreshdeskSiteConfigExtension extends DataExtension
{
private static $db = [
'CreateFreshdeskTicket' => 'Boolean(0)',
'FreshdeskGroupID' => 'Varchar(20)',
'FreshdeskProductID' => 'Varchar(20)',
];

public function updateCMSFields(FieldList $fields)
{
if (!Freshdesk::IsAvailable()) {
$fields->addFieldToTab('Root.Freshdesk', new LiteralField('MissingFreshdeskKey', '<p class="message warning">Freshdesk environment variables not defined, integration is disabled</p>'));
}

$fields->addFieldsToTab('Root.Freshdesk', [
new CheckboxField('CreateFreshdeskTicket', 'Create Freshdesk tickets for certificates about to expire'),
$groupId = new NumericField('FreshdeskGroupID', 'Freshdesk Group ID'),
$productId = new NumericField('FreshdeskProductID', 'Freshdesk Product ID'),
]);

$groupId->setDescription('Numeric ID of the group to triage the ticket to');
$productId->setDescription('Numeric ID of the product this ticket belongs to');
}
}
6 changes: 3 additions & 3 deletions mysite/code/Jobs/CheckSSLCertificates.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ protected function notifyNewCertificate($site, $cert)
$settings = [
'username' => SiteConfig::current_site_config()->Title,
'channel' => SiteConfig::current_site_config()->SlackChannel,
'icon' => SiteConfig::current_site_config()->SlackEmoji
'icon' => SiteConfig::current_site_config()->SlackEmoji,
];

$client = new Maknz\Slack\Client(SLACK_WEBHOOK_URL, $settings);
Expand Down Expand Up @@ -200,7 +200,7 @@ protected function notifyFailure($site, $error)
$settings = [
'username' => SiteConfig::current_site_config()->Title,
'channel' => SiteConfig::current_site_config()->SlackChannel,
'icon' => SiteConfig::current_site_config()->SlackEmoji
'icon' => SiteConfig::current_site_config()->SlackEmoji,
];

$client = new Maknz\Slack\Client(SLACK_WEBHOOK_URL, $settings);
Expand Down Expand Up @@ -229,7 +229,7 @@ protected function notifyCommonNameMismatch($site, $error)
$settings = [
'username' => SiteConfig::current_site_config()->Title,
'channel' => SiteConfig::current_site_config()->SlackChannel,
'icon' => SiteConfig::current_site_config()->SlackEmoji
'icon' => SiteConfig::current_site_config()->SlackEmoji,
];

$client = new Maknz\Slack\Client(SLACK_WEBHOOK_URL, $settings);
Expand Down
129 changes: 129 additions & 0 deletions mysite/code/Jobs/CreateFreshdeskTickets.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

/**
* Goes through all domains, checks their certificate expiry, and creates or upgrades
* Freshdesk tickets for the approaching expiration.
*/
class CreateFreshdeskTickets implements CronTask
{
use CronTaskUtilities;

/**
* @return string
*/
public function getSchedule()
{
return '0/30 * * * *';
}

public function process()
{
$siteConfig = SiteConfig::current_site_config();
if (!Freshdesk::IsAvailable() || !$siteConfig->CreateFreshdeskTicket) {
$this->log('Freshdesk not enabled, task not running', SS_Log::INFO);

return;
}

if (!$siteConfig->FreshdeskGroupID) {
$this->log('FreshdeskGroupId is empty or not numeric, task not running', SS_Log::INFO);

return;
}

$freshdesk = new Freshdesk();
$domains = Domain::get()->filter(['Enabled' => 1]);

$this->log('Number of existing domains: '.$domains->count(), SS_Log::INFO);

$alertDays = [
Freshdesk::PRIORITY_LOW => $siteConfig->OpsGenieDaysUntilP5,
Freshdesk::PRIORITY_MEDIUM => $siteConfig->OpsGenieDaysUntilP3,
Freshdesk::PRIORITY_HIGH => $siteConfig->OpsGenieDaysUntilP2,
Freshdesk::PRIORITY_URGENT => $siteConfig->OpsGenieDaysUntilP1,
];

$startAlerting = max(array_values($alertDays));

foreach ($domains as $d) {
if (!$d->CurrentCertificate()->exists()) {
continue;
}

$cert = $d->CurrentCertificate();

$this->log('Checking '.$d->Domain, SS_Log::INFO);
$this->log('Days until expiration: '.$cert->DaysUntilExpiration, SS_Log::DEBUG);

// Skip if we're outside the alerting threshold
if ($cert->DaysUntilExpiration > $startAlerting) {
// If there is a current OpsGenie alert try and close it as the cert may have been updated
if ($d->FreshdeskID) {
$this->log('Closing existing Freshdesk ticket '.$d->FreshdeskID, SS_Log::INFO);

$freshdesk->closeTicket($d->FreshdeskID, $this->createTicketBody($d, $cert, 'FreshdeskTicketClosed'));

$d->FreshdeskID = '';
$d->FreshdeskPriority = '';
$d->write();
}

continue;
}

$priority = $this->closestNumber($cert->DaysUntilExpiration, $alertDays);
$this->log('Current alert priority: '.$priority, SS_Log::DEBUG);

// Create an Freshdesk ticket if one isn't already made
if (!$d->FreshdeskID) {
$this->log('Creating Freshdesk ticket', SS_Log::INFO);

$ticket = $freshdesk->createTicket([
'subject' => $d->Domain.' certificate expires '.$cert->ValidTo,
'description' => $this->createTicketBody($d, $cert),
'priority' => $priority,
'group_id' => (int) $siteConfig->FreshdeskGroupID,
'product_id' => (int) $siteConfig->FreshdeskProductID,
'requester_id' => (int) FRESHDESK_USER_ID,
'status' => Freshdesk::STATUS_OPEN,
'tags' => ['ssl', 'locksmith'],
]);

$d->FreshdeskID = $ticket->id;
$d->FreshdeskPriority = $priority;

$d->write();
} elseif ((int) $priority !== (int) $d->FreshdeskPriority) {
// Upgrade the priority if its different
$this->log('Upgrading Freshdesk ticket from '.$d->FreshdeskPriority.' to '.$priority, SS_Log::INFO);
$freshdesk->addNote($d->FreshdeskID, 'Escalating Freshdesk ticket priority from '.Freshdesk::PriorityAsString($d->FreshdeskPriority).' to '.Freshdesk::PriorityAsString($priority));

$freshdesk->updateTicket($d->FreshdeskID, [
'priority' => $priority,
]);

$d->FreshdeskPriority = $priority;
$d->write();
}
}
}

/**
* Creates the body of the ticket with information about the domain.
*
* @param $domain
* @param $cert
* @param string $template
*
* @return HTMLText
*/
private function createTicketBody($domain, $cert, $template = 'FreshdeskTicket')
{
$arrayData = new ArrayData([
'Domain' => $domain,
'Certificate' => $cert,
]);

return $arrayData->renderWith($template)->RAW();
}
}
8 changes: 8 additions & 0 deletions mysite/code/Jobs/DailyExpirationReminder.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ public function process()
$cert->ValidTo
);

if (Freshdesk::IsAvailable() && $d->FreshdeskID) {
$line .= sprintf(
', <%s/helpdesk/tickets/%s|open in Freshdesk>',
FRESHDESK_DOMAIN,
$d->FreshdeskID
);
}

$alerts[$priority][] = $line;
}

Expand Down
8 changes: 8 additions & 0 deletions mysite/code/Model/Certificate.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,12 @@ public function getDaysUntilExpiration()
// We use %r%a to ensure we provide a - if the number of days is a negative
return $earlier->diff($later)->format('%r%a');
}

/**
* @return bool True if this is a LE cert
*/
public function getIsLetsEncrypt()
{
return false !== stripos($this->Issuer, "Let's Encrypt");
}
}
11 changes: 11 additions & 0 deletions mysite/code/Model/Domain.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class Domain extends DataObject
'AlertPriority' => 'Text',
'AlertedCommonNameMismatch' => 'Boolean(0)',
'HasBeenChecked' => 'Boolean(0)',
'FreshdeskID' => 'Text',
'FreshdeskPriority' => 'Text',
];

private static $has_many = [
Expand Down Expand Up @@ -63,11 +65,20 @@ public function getCMSFields()
->setRows(1)
->setDescription('The ID of the OpsGenie alert for this domain. Set to empty if there is no alert');

$fields->dataFieldByName('FreshdeskID')
->setRows(1)
->setDescription('The ID of the Freshdesk ticket for this domain. Set to empty if there is no alert');

$fields->dataFieldByName('AlertPriority')
->setRows(1)
->setReadonly(true)
->setDescription('The current status of the OpsGenie alert (P5 to P1)');

$fields->dataFieldByName('FreshdeskPriority')
->setRows(1)
->setReadonly(true)
->setDescription('The current priority of the Freshdesk ticket (1 to 4)');

$fields->addFieldToTab('Root.Certificates', GridField::create(
'Certificates',
'Certificates recorded for this domain',
Expand Down

0 comments on commit 7d7b8e0

Please sign in to comment.