Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
/web/themes/contrib/
/web/profiles/.gitignore
/web/profiles/contrib/
/web/private/scripts/quicksilver
/web/.eslintrc.json
/web/recipes/.gitignore
/web/recipes/contrib/
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,39 @@ After cloning this repository, configure DDEV for the project:
ddev project-init
```

5. **Set up New Relic deploy markers (one-time, per site):**

This starter logs a deploy marker to New Relic on every Pantheon
`sync_code` and `deploy` (see `pantheon.yml`). The Quicksilver script is
Pantheon's canonical `new_relic_deploy.php`, vendored via Composer from
[`pantheon-systems/quicksilver-examples`](https://github.com/pantheon-systems/quicksilver-examples/tree/main/new_relic_deploy).
It reads the New Relic API key from a Pantheon secret, so **each new site
created from this starter must complete the following once** or the script
will exit with `ALERT! No New Relic metadata could be found.`

1. In the Pantheon Dashboard, activate **New Relic Performance Monitoring**
(New Relic Pro) for the site.
2. Create a [New Relic **User key**](https://one.newrelic.com/launcher/api-keys-ui.api-keys-launcher)
(a User key — not a license/ingest key).
3. Store it as a site secret named `new_relic_api_key` using the
[Terminus Secrets Manager plugin](https://docs.pantheon.io/terminus/plugins/directory#secrets-manager).
The secret **must** use `--type=runtime` and a scope that includes `web`:
```shell
terminus secret:site:set <site> new_relic_api_key --scope=web --type=runtime <NEW_RELIC_USER_KEY>
```
> The secret name must stay `new_relic_api_key` to match `pantheon.yml`
> and the script. If you rename it, update both.
4. Verify it was stored:
```shell
terminus secret:site:list <site>
```
5. Trigger a deploy (or run a `sync_code`) and confirm a deployment marker
appears in New Relic for the environment.

See Pantheon's
[New Relic Deploy README](https://github.com/pantheon-systems/quicksilver-examples/blob/main/new_relic_deploy/README.md)
for full details.

## DDEV Commands

This project uses the [kanopi/ddev-kanopi-drupal](https://github.com/kanopi/ddev-kanopi-drupal) add-on which provides 27+ custom commands for Drupal development.
Expand Down
4 changes: 2 additions & 2 deletions pantheon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ workflows:
script: private/scripts/drush_config_import/drush_config_import.php
- type: webphp
description: Log to New Relic
script: private/scripts/sync_code/new_relic_deploy.php
script: private/scripts/quicksilver/new_relic_deploy/new_relic_deploy.php
# Log to New Relic when deploying to test or live.
deploy:
after:
- type: webphp
description: Log to New Relic
script: private/scripts/deploy/new_relic_deploy.php
script: private/scripts/quicksilver/new_relic_deploy/new_relic_deploy.php
- type: webphp
description: Database updates, config import.
script: private/scripts/drush_config_import/drush_config_import.php
75 changes: 0 additions & 75 deletions web/private/scripts/deploy/new_relic_deploy.php

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<?php
// No need to log this script operation in New Relic's stats.
// PROTIP: you might also want to use this snippet if you have PHP code handling
// very fast things like redirects or the like.
if (extension_loaded('newrelic')) {
newrelic_ignore_transaction();
}

define("API_KEY_SECRET_NAME", "new_relic_api_key");

$data = get_nr_connection_info();
// Fail fast if we're not going to be able to call New Relic.
if ($data == false) {
echo "\n\nALERT! No New Relic metadata could be found.\n\n";
exit();
}

$app_guid = get_app_guid($data['api_key'], $data['app_name']);

if (empty($app_guid)) {
echo "Error: No New Relic app found with name " . $data['app_name'];
exit();
}

// This is one example that handles code pushes, dashboard
// commits, and deploys between environments. To make sure we
// have good deploy markers, we gather data differently depending
// on the context.

if (in_array($_POST['wf_type'], ['sync_code','sync_code_with_build'])) {
// commit 'subject'
$description = trim(`git log --pretty=format:"%s" -1`);
$revision = trim(`git log --pretty=format:"%h" -1`);
if ($_POST['user_role'] == 'super') {
// This indicates an in-dashboard SFTP commit.
$user = trim(`git log --pretty=format:"%ae" -1`);
$changelog = trim(`git log --pretty=format:"%b" -1`);
$changelog .= ' (Commit made via Pantheon dashboard.)';
}
else {
$user = $_POST['user_email'];
$changelog = trim(`git log --pretty=format:"%b" -1`);
$changelog .= ' (Triggered by remote git push.)';
}
}
elseif ($_POST['wf_type'] == 'deploy') {
// Topline description:
$description = 'Deploy to environment triggered via Pantheon';
// Find out if there's a deploy tag:
$revision = `git describe --tags --abbrev=0`;
// Get the annotation:
$changelog = `git tag -l -n99 $revision`;
$user = $_POST['user_email'];
}

// clean up the git output
$revision = rtrim($revision, "\n");
$changelog = rtrim($changelog, "\n");
$changelog = str_replace('\'','',$changelog);
$changelog = str_replace('"','',$changelog);

$deployment_data = [
"deployment" => [
"revision" => $revision,
"changelog" => $changelog,
"description" => $description,
"user" => $user,
]
];

echo "Logging deployment in New Relic App $app_guid...\n";
$response = create_newrelic_deployment_change_tracking($data['api_key'], $app_guid, $user, $revision, $changelog, $description);

echo "\nResponse from New Relic:" . $response;

echo "\nDone!\n";

/**
* Gets the New Relic API Key so that further requests can be made.
*
* Also gets New Relic's name for the given environment.
*/
function get_nr_connection_info() {
$output = array();

$output['app_name'] = ini_get('newrelic.appname');
if (function_exists('pantheon_get_secret')) {
$output['api_key'] = pantheon_get_secret(API_KEY_SECRET_NAME);
}

return $output;
}

// Get GUID of the current environment.
function get_app_guid(string $api_key, string $app_name): string {
$url = 'https://api.newrelic.com/graphql';
$headers = ['Content-Type: application/json', 'API-Key: ' . $api_key];

// Updated entitySearch query with name filter
$data = '{ "query": "{ actor { entitySearch(query: \\"(domain = \'APM\' and type = \'APPLICATION\' and name = \'' . $app_name . '\')\\") { count query results { entities { entityType name guid } } } } }" }';

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

$response = curl_exec($ch);
curl_close($ch);


$decoded_response = json_decode($response, true);

// Error handling for API response.
if (isset($decoded_response['errors'])) {
echo "Error: " . $decoded_response['errors'][0]['message'] . "\n";
return '';
}
if (!isset($decoded_response['data']['actor']['entitySearch']['results']['entities']) || !is_array($decoded_response['data']['actor']['entitySearch']['results']['entities'])) {
echo "Error: No entities found in New Relic response\n";
return '';
}

$entities = $decoded_response['data']['actor']['entitySearch']['results']['entities'];
// Since we filtered by name, the first entity should be the correct one.
if (isset($entities[0]['guid'])) {
return $entities[0]['guid'];
}
return '';
}

function create_newrelic_deployment_change_tracking(string $api_key, string $entityGuid, string $user, string $version, string $changelog, string $description): string {
$url = 'https://api.newrelic.com/graphql';
$headers = ['Content-Type: application/json', 'API-Key: ' . $api_key];

$timestamp = round(microtime(true) * 1000);

// Construct the mutation with dynamic variables
$data = '{
"query": "mutation { changeTrackingCreateDeployment(deployment: { version: \\"' . $version . '\\" user: \\"' . $user . '\\" timestamp: ' . $timestamp . ' entityGuid: \\"' . $entityGuid . '\\" description: \\"' . $description . '\\" changelog: \\"' . $changelog . '\\" }) { changelog deploymentId description entityGuid timestamp user version } }"
}';

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

$response = curl_exec($ch);
curl_close($ch);
return $response;
}
75 changes: 0 additions & 75 deletions web/private/scripts/sync_code/new_relic_deploy.php

This file was deleted.