diff --git a/.gitignore b/.gitignore index 73d1f71..4c1d833 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/README.md b/README.md index 59edd0e..301ead1 100644 --- a/README.md +++ b/README.md @@ -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 new_relic_api_key --scope=web --type=runtime + ``` + > 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 + ``` + 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. diff --git a/pantheon.yml b/pantheon.yml index c1c0585..e9cf9ac 100644 --- a/pantheon.yml +++ b/pantheon.yml @@ -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 diff --git a/web/private/scripts/deploy/new_relic_deploy.php b/web/private/scripts/deploy/new_relic_deploy.php deleted file mode 100644 index 612d5e5..0000000 --- a/web/private/scripts/deploy/new_relic_deploy.php +++ /dev/null @@ -1,75 +0,0 @@ - [ + "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; +} diff --git a/web/private/scripts/sync_code/new_relic_deploy.php b/web/private/scripts/sync_code/new_relic_deploy.php deleted file mode 100644 index 612d5e5..0000000 --- a/web/private/scripts/sync_code/new_relic_deploy.php +++ /dev/null @@ -1,75 +0,0 @@ -