From 828acc9b9adad7361649f454bc1e6e4a2756b46a Mon Sep 17 00:00:00 2001 From: Alex Finnarn Date: Thu, 15 Feb 2024 15:26:28 -0500 Subject: [PATCH] VACMS-16853: next build branches ftw (#17154) * add next-build as an option for requesting a content release * only override needed form fields for content release form * be more specific with repo roots in settings for different envs * fix broken unit test * fix broken integration test * add api client for next-build * try different file root location * add frontend information to payload, pass info to build trigger * add start to next-build frontend script * update next-build-frontend.sh script * further separate content release state machines * remove seperate release state manager * remove all references to simulatenous building using current pattern * simpler build trigger within form submission * debug Tugboat * filename change for queue runner * try kicking off script via cron * add more debugging to cron run * correct git location for next-build * make build script executable * cleanup code * add documentation * more code cleanup * update docs to trigger after failed tugboat * cleanup code * Update scripts/next-build-frontend.sh Co-authored-by: Tanner Heffner * Update scripts/next-build-frontend.sh Co-authored-by: Tanner Heffner * Update scripts/next-build-frontend.sh Co-authored-by: Tanner Heffner * fixes from code review --------- Co-authored-by: Tanner Heffner --- .tugboat/config.yml | 7 + READMES/cms-content-release.md | 30 +- ...xt.next_site.next_build_preview_server.yml | 4 +- .../custom/va_gov_content_release/README.md | 13 + .../src/Form/NextGitForm.php | 399 ++++++++++++++++++ .../FrontendVersionSearch.php | 30 +- .../va_gov_content_release.module | 6 - .../va_gov_content_release.routing.yml | 8 + .../va_gov_content_release.services.yml | 2 + .../Factory/BranchSearchFactory.php | 8 + .../Factory/BranchSearchFactoryInterface.php | 8 + .../Repository/Factory/RepositoryFactory.php | 7 + .../Factory/RepositoryFactoryInterface.php | 11 +- .../Settings/RepositorySettings.php | 2 +- .../Settings/RepositorySettingsInterface.php | 5 + .../custom/va_gov_git/va_gov_git.services.yml | 4 + .../Api/Client/Factory/ApiClientFactory.php | 9 + .../Factory/ApiClientFactoryInterface.php | 11 + .../va_gov_github/va_gov_github.services.yml | 4 + docroot/sites/default/settings.php | 1 + .../default/settings/settings.tugboat.php | 1 + scripts/next-build-frontend.sh | 120 ++++++ scripts/next-build.sh | 13 +- scripts/next-install.sh | 14 +- scripts/queue_runner/next_queue_runner.sh | 6 + .../Settings/RepositorySettingsTest.php | 5 + .../Settings/RepositorySettingsTest.php | 7 + 27 files changed, 717 insertions(+), 18 deletions(-) create mode 100644 docroot/modules/custom/va_gov_content_release/README.md create mode 100644 docroot/modules/custom/va_gov_content_release/src/Form/NextGitForm.php delete mode 100644 docroot/modules/custom/va_gov_content_release/va_gov_content_release.module create mode 100644 scripts/next-build-frontend.sh create mode 100644 scripts/queue_runner/next_queue_runner.sh diff --git a/.tugboat/config.yml b/.tugboat/config.yml index 6e628ef6c4..e1f37c407e 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -226,6 +226,13 @@ services: - mv "${TUGBOAT_ROOT}/scripts/queue_runner/queue_runner.sh" /etc/service/drupal_events/run - chmod +x /etc/service/drupal_events/run + # Separate process for next-build preview. + - mkdir -p /etc/service/next_build + - mv "${TUGBOAT_ROOT}/scripts/queue_runner/next_queue_runner.sh" /etc/service/next_build/run + - chmod +x /etc/service/next_build/run + # Need to make build script executable so the runner can run it. + - chmod +x "${TUGBOAT_ROOT}/scripts/next-build-frontend.sh" + clone: # This j2 command is shared in both the build & clone stages. If modifying, change the other too. - j2 "${TUGBOAT_ROOT}/.tugboat/.env.j2" -o "${TUGBOAT_ROOT}/.env" diff --git a/READMES/cms-content-release.md b/READMES/cms-content-release.md index 8d9119600e..0c8c93e598 100644 --- a/READMES/cms-content-release.md +++ b/READMES/cms-content-release.md @@ -116,7 +116,6 @@ sequenceDiagram Complete->>+Ready: Content release workflow has completed ``` - # Environment specific details ## BRD Production @@ -144,6 +143,35 @@ The Tugboat and local development versions of the release content page do not tr For more information on creating or releasing content from a preview environment, see [Environments](./environments.md). +### Next Build Releases + +The upcoming static frontend "next-build" can be rebuilt using different versions of next-build and vets-website. It +is a simpler process than the current content-build workflow. + +1. Go to "/admin/content/deploy/next". +2. If the form elements are disabled, then a lock file exists preventing another build from being triggered. You + can skip to step #6. +2. Choose a version for next-build or leave at default. +3. Choose a version for vets-website or leave at default. When content-build is releasing, these form fields might + be disabled. We can't change the vets-website version while another frontend build is running. +4. Click "Release Content" to set the versions of next-build and vets-website as well as write a "buildrequest" file. +5. A `scripts/queue_runner/next_queue_runner.sh` script continuously runs in the background looking for the + "buildrequest" file and then start a build if found. Locally, the script has to be triggered manually. See the + [caveats](#caveats) section for more information. +6. Back on "/admin/content/deploy/next" you can view the build log via a link in the "Status" section of the + "Next Build Information" block. +7. Once the build completes no new build will be triggered until you click to release content again. +8. View the frontend at the provided "View Preview" link in the "Next Build Information" block. + +#### Caveats + +There are some caveats to the process outlined above. + +- **Manually running the background script** - On `ddev` the `queue_runner` scripts aren't running continuously in + background jobs. So you must `ddev ssh && ./scripts/queue_runner/` to kick off the content build or next build + release locally. In the future, it might be a good idea to use `system.d` or `supervisor` or something else to + keep the background jobs going locally just like on Tugoboat. + ## Troubleshooting Sometimes the state gets stuck and needs to be reset. It can be done with a drush command diff --git a/config/sync/next.next_site.next_build_preview_server.yml b/config/sync/next.next_site.next_build_preview_server.yml index 3e3a99c8bb..caf6c88f5d 100644 --- a/config/sync/next.next_site.next_build_preview_server.yml +++ b/config/sync/next.next_site.next_build_preview_server.yml @@ -4,8 +4,8 @@ status: true dependencies: { } id: next_build_preview_server label: 'Next Build Preview Server' -base_url: 'http://localhost:3000' -preview_url: 'http://localhost:3000/api/preview' +base_url: 'http://localhost:3999' +preview_url: 'http://localhost:3999/api/preview' preview_secret: secret revalidate_url: '' revalidate_secret: '' diff --git a/docroot/modules/custom/va_gov_content_release/README.md b/docroot/modules/custom/va_gov_content_release/README.md new file mode 100644 index 0000000000..226790fb81 --- /dev/null +++ b/docroot/modules/custom/va_gov_content_release/README.md @@ -0,0 +1,13 @@ +# VA.gov Content Release + +va.gov is built using two separate frontend systems with a third being built: + +- content-build - This is the frontend for generating static content files. You can read more about the project here: + https://github.com/department-of-veterans-affairs/content-build +- vets-website - This is the frontend for integrating React widgets. You can read more about the project here: + https://github.com/department-of-veterans-affairs/vets-website +- next-build - This is the new frontend for generating static content files. You can read more about the project here: + https://github.com/department-of-veterans-affairs/next-build + +More detailed documentation about how these frontends are build and released can be found in the main READMEs +directory on the [CMS Content Release](../../../../READMES/cms-content-release.md) page. diff --git a/docroot/modules/custom/va_gov_content_release/src/Form/NextGitForm.php b/docroot/modules/custom/va_gov_content_release/src/Form/NextGitForm.php new file mode 100644 index 0000000000..e20b3870b9 --- /dev/null +++ b/docroot/modules/custom/va_gov_content_release/src/Form/NextGitForm.php @@ -0,0 +1,399 @@ +frontendVersion = $frontendVersion; + $this->fileSystem = $fileSystem; + $this->config = $config; + $this->state = $state; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('va_gov_content_release.frontend_version'), + $container->get('file_system'), + $container->get('config.factory'), + $container->get('state') + ); + } + + /** + * Build the form. + * + * @param array $form + * Default form array structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Object containing current form state. + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form['build_request']['description'] = [ + '#prefix' => '

', + '#markup' => $this->t('Release content to update the front end of this environment with the latest published content changes.'), + '#suffix' => '

', + ]; + + $form['build_request']['next_build_selection'] = [ + '#title' => $this->t('Which version of next-build would you like to use?'), + '#type' => 'radios', + '#options' => [ + 'default' => $this->t('Use default - the next-build version from the time this demo environment was created.'), + 'choose' => $this->t('Select a different next-build branch/pull request - for example, to see your content in a newer frontend design.'), + ], + '#default_value' => 'default', + ]; + + $form['build_request']['next_build_git_ref'] = [ + '#type' => 'textfield', + '#title' => $this->t('Select branch/pull request'), + '#description' => $this->t('Start typing to select a branch for the next-build version you want to use.'), + '#autocomplete_route_name' => 'va_gov_content_release.frontend_version_autocomplete', + '#autocomplete_route_parameters' => [ + 'frontend' => 'next_build', + 'count' => 10, + ], + '#size' => 72, + '#maxlength' => 1024, + '#hidden' => TRUE, + '#states' => [ + 'visible' => [':input[name="next_build_selection"]' => ['value' => 'choose']], + ], + ]; + + $form['build_request']['vets_website_selection'] = [ + '#title' => $this->t('Which version of vets-website would you like to use?'), + '#type' => 'radios', + '#options' => [ + 'default' => $this->t('Use default - the vets-website version from the time this demo environment was created.'), + 'choose' => $this->t('Select a different vets-website branch/pull request - for example, to see your content in a newer frontend design.'), + ], + '#default_value' => 'default', + ]; + + $form['build_request']['vets_website_git_ref'] = [ + '#type' => 'textfield', + '#title' => $this->t('Select branch/pull request'), + '#description' => $this->t('Start typing to select a branch for the vets-website version you want to use.'), + '#autocomplete_route_name' => 'va_gov_content_release.frontend_version_autocomplete', + '#autocomplete_route_parameters' => [ + 'frontend' => 'vets_website', + 'count' => 10, + ], + '#size' => 72, + '#maxlength' => 1024, + '#hidden' => TRUE, + '#states' => [ + 'visible' => [':input[name="vets_website_selection"]' => ['value' => 'choose']], + ], + ]; + + $form['build_request']['actions']['#type'] = 'actions'; + $form['build_request']['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Release Content'), + '#button_type' => 'primary', + ]; + + // Lock the vets-website form fields if a content-build is in progress. + $build_status = $this->state->get('va_gov_build_trigger.release_state'); + if ($build_status !== ReleaseStateManager::STATE_READY) { + $form['build_request']['vets_website_selection']['#disabled'] = TRUE; + $form['build_request']['vets_website_git_ref']['#disabled'] = TRUE; + } + + // Disable form changes and submission if a build is in progress. + if (file_exists($this->fileSystem->realpath('public://' . self::LOCK_FILE_NAME))) { + $form['build_request']['next_build_selection']['#disabled'] = TRUE; + $form['build_request']['next_build_git_ref']['#disabled'] = TRUE; + $form['build_request']['vets_website_selection']['#disabled'] = TRUE; + $form['build_request']['vets_website_git_ref']['#disabled'] = TRUE; + $form['build_request']['actions']['submit']['#disabled'] = TRUE; + + $target_url = Url::fromUserInput("/sites/default/files/next-build.txt"); + $build_log_text = Link::fromTextAndUrl('Build is in progress. View log file', $target_url); + } + else { + $build_log_text = 'Build is not in progress.'; + } + + // Set variables needed for build status information. + $lock_file_text = $this->getFileLink(self::LOCK_FILE_NAME); + $request_file_text = $this->getFileLink(self::REQUEST_FILE_NAME); + $next_build_version = $this->frontendVersion->getVersion(Frontend::NextBuild); + $vets_website_version = $this->frontendVersion->getVersion(Frontend::VetsWebsite); + $view_preview = $this->getPreviewLink(); + $last_build_time = $this->state->get('next_build.status.last_build_date', 'N/A'); + $form['content_release_status_block'] = [ + '#theme' => 'status_report_grouped', + '#grouped_requirements' => [ + [ + 'title' => $this->t('Next Build Information'), + 'type' => 'content-release-status', + 'items' => [ + 'status' => [ + 'title' => $this->t('Status'), + 'value' => $build_log_text, + ], + 'lock_file' => [ + 'title' => $this->t('Lock File'), + 'value' => $lock_file_text, + ], + 'request_file' => [ + 'title' => $this->t('Request File'), + 'value' => $request_file_text, + ], + 'next_build_version' => [ + 'title' => $this->t('Next-build Version'), + 'value' => $next_build_version, + ], + 'vets_website_version' => [ + 'title' => $this->t('Vets-website Version'), + 'value' => $vets_website_version, + ], + 'view_preview' => [ + 'title' => $this->t('View Preview'), + 'value' => $view_preview, + ], + 'last_build_time' => [ + 'title' => $this->t('Last Build Time'), + 'value' => $last_build_time, + ], + ], + ], + ], + ]; + + return $form; + } + + /** + * Get the text for a file. + * + * @param string $file_name + * The name of the file. + * + * @return \Drupal\Core\Link|string + * The file link. + */ + private function getFileLink(string $file_name): Link|string { + $file_path = $this->fileSystem->realpath("public://$file_name"); + if (file_exists($file_path)) { + $target_url = Url::fromUserInput("/sites/default/files/$file_name"); + return Link::fromTextAndUrl($file_name, $target_url); + } + else { + return 'does not exist'; + } + } + + /** + * Get the preview link. + * + * @return \Drupal\Core\Link + * The preview link. + */ + private function getPreviewLink(): Link { + $frontend_base_url = $this->config + ->get('next.next_site.next_build_preview_server') + ->get('base_url'); + $target_url = Url::fromUri($frontend_base_url, ['attributes' => ['target' => '_blank']]); + return Link::fromTextAndUrl($this->t('View front end'), $target_url); + } + + /** + * Submit the build trigger form. + * + * @param array $form + * Default form array structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Object containing current form state. + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->submitFormForFrontend(Frontend::NextBuild, $form_state); + $this->submitFormForFrontend(Frontend::VetsWebsite, $form_state); + + $lock_file = $this->fileSystem->realpath('public://' . self::LOCK_FILE_NAME); + if (file_exists($lock_file)) { + $this->messenger() + ->addMessage($this->t('The build is in progress. Please wait for the build to complete.')); + } + else { + $this->fileSystem->saveData( + 'Build me, Seymour!', + 'public://' . self::REQUEST_FILE_NAME, + 1); + $this->messenger()->addMessage($this->t('Build request file set.')); + } + } + + /** + * Submit the form. + * + * @param \Drupal\va_gov_content_release\Frontend\FrontendInterface $frontend + * The frontend whose version we are managing. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Object containing current form state. + */ + protected function submitFormForFrontend( + FrontendInterface $frontend, + FormStateInterface $form_state + ) { + $selectionName = $frontend->getRawValue() . '_selection'; + if ($form_state->getValue($selectionName) === 'default') { + $this->resetFrontendVersion($frontend, $form_state); + } + else { + $this->setFrontendVersion($frontend, $form_state); + } + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $this->validateFormForFrontend(Frontend::NextBuild, $form_state); + $this->validateFormForFrontend(Frontend::VetsWebsite, $form_state); + } + + /** + * Validate the form. + * + * @param \Drupal\va_gov_content_release\Frontend\FrontendInterface $frontend + * The frontend whose version we are managing. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Object containing current form state. + */ + protected function validateFormForFrontend( + FrontendInterface $frontend, + FormStateInterface $form_state + ) { + $selectionName = $frontend->getRawValue() . '_selection'; + $gitRefName = $frontend->getRawValue() . '_git_ref'; + if ($form_state->getValue($selectionName) !== 'default') { + if (empty($this->getGitRef($frontend, $form_state))) { + $form_state->setErrorByName($gitRefName, + $this->t('Invalid selection.')); + } + } + } + + /** + * Reset the frontend version. + * + * @param \Drupal\va_gov_content_release\Frontend\FrontendInterface $frontend + * The frontend whose version we are resetting. + */ + public function resetFrontendVersion(FrontendInterface $frontend) { + $this->frontendVersion->resetVersion($frontend); + } + + /** + * Set the frontend version according to the form. + * + * @param \Drupal\va_gov_content_release\Frontend\FrontendInterface $frontend + * The frontend whose version we are setting. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Object containing current form state. + */ + public function setFrontendVersion( + FrontendInterface $frontend, + FormStateInterface $form_state + ) { + $this->frontendVersion->setVersion($frontend, + $this->getGitRef($frontend, $form_state)); + } + + /** + * Parse a git ref out of the `git_ref` field value. + * + * @param \Drupal\va_gov_content_release\Frontend\FrontendInterface $frontend + * The frontend whose version we are setting. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Object containing current form state. + * + * @return string + * A standalone git ref, or an empty string. + */ + public function getGitRef( + FrontendInterface $frontend, + FormStateInterface $form_state + ): string { + // If they selected a specific git ref, use that. + $gitRefName = $frontend->getRawValue() . '_git_ref'; + $formValue = $form_state->getValue($gitRefName); + $result = ''; + if (preg_match("/.+\\s\\(([^\\)]+)\\)/", $formValue, $matches)) { + $result = $matches[1]; + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function getFormId(): string { + return 'va_gov_content_release_next_git_form'; + } + +} diff --git a/docroot/modules/custom/va_gov_content_release/src/FrontendVersionSearch/FrontendVersionSearch.php b/docroot/modules/custom/va_gov_content_release/src/FrontendVersionSearch/FrontendVersionSearch.php index d2d6145d39..1420453091 100644 --- a/docroot/modules/custom/va_gov_content_release/src/FrontendVersionSearch/FrontendVersionSearch.php +++ b/docroot/modules/custom/va_gov_content_release/src/FrontendVersionSearch/FrontendVersionSearch.php @@ -50,6 +50,20 @@ class FrontendVersionSearch implements FrontendVersionSearchInterface { */ protected $logger; + /** + * The branch search service for `vets-website`. + * + * @var \Drupal\va_gov_git\BranchSearch\BranchSearchInterface + */ + protected $nbBranchSearch; + + /** + * The API client for `vets-website`. + * + * @var \Drupal\va_gov_github\Api\Client\ApiClientInterface + */ + protected $nbApiClient; + /** * Constructor. * @@ -63,19 +77,27 @@ class FrontendVersionSearch implements FrontendVersionSearchInterface { * The API client for `vets-website`. * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory * The logger factory service. + * @param \Drupal\va_gov_git\BranchSearch\BranchSearchInterface $nbBranchSearch + * The branch search service for `next-build`. + * @param \Drupal\va_gov_github\Api\Client\ApiClientInterface $nbApiClient + * The API client for `next-build`. */ public function __construct( BranchSearchInterface $cbBranchSearch, ApiClientInterface $cbApiClient, BranchSearchInterface $vwBranchSearch, ApiClientInterface $vwApiClient, - LoggerChannelFactoryInterface $loggerFactory + LoggerChannelFactoryInterface $loggerFactory, + BranchSearchInterface $nbBranchSearch, + ApiClientInterface $nbApiClient, ) { $this->cbBranchSearch = $cbBranchSearch; $this->cbApiClient = $cbApiClient; $this->vwBranchSearch = $vwBranchSearch; $this->vwApiClient = $vwApiClient; $this->logger = $loggerFactory->get('va_gov_content_release'); + $this->nbBranchSearch = $nbBranchSearch; + $this->nbApiClient = $nbApiClient; } /** @@ -95,6 +117,9 @@ protected function getBranchSearch(FrontendInterface $frontend) : BranchSearchIn case $frontend->isVetsWebsite(): return $this->vwBranchSearch; + case $frontend->isNextBuild(): + return $this->nbBranchSearch; + default: throw new \InvalidArgumentException('Invalid frontend: ' . $frontend->getRawValue()); } @@ -117,6 +142,9 @@ protected function getApiClient(FrontendInterface $frontend) : ApiClientInterfac case $frontend->isVetsWebsite(): return $this->vwApiClient; + case $frontend->isNextBuild(): + return $this->nbApiClient; + default: throw new \InvalidArgumentException('Invalid frontend: ' . $frontend->getRawValue()); } diff --git a/docroot/modules/custom/va_gov_content_release/va_gov_content_release.module b/docroot/modules/custom/va_gov_content_release/va_gov_content_release.module deleted file mode 100644 index 35beaf8b98..0000000000 --- a/docroot/modules/custom/va_gov_content_release/va_gov_content_release.module +++ /dev/null @@ -1,6 +0,0 @@ -repositoryFactory->getVetsWebsite(), $this->loggerFactory); } + /** + * {@inheritDoc} + */ + public function getNextBuild(): BranchSearchInterface { + return new BranchSearch($this->repositoryFactory->getNextBuild(), + $this->loggerFactory); + } + } diff --git a/docroot/modules/custom/va_gov_git/src/BranchSearch/Factory/BranchSearchFactoryInterface.php b/docroot/modules/custom/va_gov_git/src/BranchSearch/Factory/BranchSearchFactoryInterface.php index 8d67cb0b1f..616cc686ce 100644 --- a/docroot/modules/custom/va_gov_git/src/BranchSearch/Factory/BranchSearchFactoryInterface.php +++ b/docroot/modules/custom/va_gov_git/src/BranchSearch/Factory/BranchSearchFactoryInterface.php @@ -55,4 +55,12 @@ public function getContentBuild(): BranchSearchInterface; */ public function getVetsWebsite(): BranchSearchInterface; + /** + * Get the next-build branch search service. + * + * @return \Drupal\va_gov_git\BranchSearch\BranchSearchInterface + * The next-build branch search object. + */ + public function getNextBuild(): BranchSearchInterface; + } diff --git a/docroot/modules/custom/va_gov_git/src/Repository/Factory/RepositoryFactory.php b/docroot/modules/custom/va_gov_git/src/Repository/Factory/RepositoryFactory.php index fe70477b26..4a1f8a93d4 100644 --- a/docroot/modules/custom/va_gov_git/src/Repository/Factory/RepositoryFactory.php +++ b/docroot/modules/custom/va_gov_git/src/Repository/Factory/RepositoryFactory.php @@ -65,4 +65,11 @@ public function getVetsWebsite(): RepositoryInterface { return $this->get(RepositorySettingsInterface::VETS_WEBSITE); } + /** + * {@inheritDoc} + */ + public function getNextBuild(): RepositoryInterface { + return $this->get(RepositorySettingsInterface::NEXT_BUILD); + } + } diff --git a/docroot/modules/custom/va_gov_git/src/Repository/Factory/RepositoryFactoryInterface.php b/docroot/modules/custom/va_gov_git/src/Repository/Factory/RepositoryFactoryInterface.php index 3e0a3350f2..877b59e3ae 100644 --- a/docroot/modules/custom/va_gov_git/src/Repository/Factory/RepositoryFactoryInterface.php +++ b/docroot/modules/custom/va_gov_git/src/Repository/Factory/RepositoryFactoryInterface.php @@ -10,10 +10,11 @@ * This service provides a way to create services corresponding to specific Git * repositories. * - * At this time, we're primarily interested in three repositories: + * At this time, we're primarily interested in four repositories: * - The `va.gov-cms` repository. * - The `content-build` repository. * - The `vets-website` repository. + * - The `next-build` repository. */ interface RepositoryFactoryInterface { @@ -55,4 +56,12 @@ public function getContentBuild(): RepositoryInterface; */ public function getVetsWebsite(): RepositoryInterface; + /** + * Get the next-build repository. + * + * @return \Drupal\va_gov_git\Repository\RepositoryInterface + * The next-build repository. + */ + public function getNextBuild(): RepositoryInterface; + } diff --git a/docroot/modules/custom/va_gov_git/src/Repository/Settings/RepositorySettings.php b/docroot/modules/custom/va_gov_git/src/Repository/Settings/RepositorySettings.php index 4d131e6043..c2f09cbdea 100644 --- a/docroot/modules/custom/va_gov_git/src/Repository/Settings/RepositorySettings.php +++ b/docroot/modules/custom/va_gov_git/src/Repository/Settings/RepositorySettings.php @@ -59,7 +59,7 @@ public function getPath(string $name): string { } $path = $this->settings->get($pathKey); if (empty($path)) { - throw new RepositoryPathNotSetException('Path not set for repository: ' . $name); + throw new RepositoryPathNotSetException('Path not set for repository: ' . $name . ' (' . $pathKey . ')'); } return $path; } diff --git a/docroot/modules/custom/va_gov_git/src/Repository/Settings/RepositorySettingsInterface.php b/docroot/modules/custom/va_gov_git/src/Repository/Settings/RepositorySettingsInterface.php index 15615705cc..dfcfbbca84 100644 --- a/docroot/modules/custom/va_gov_git/src/Repository/Settings/RepositorySettingsInterface.php +++ b/docroot/modules/custom/va_gov_git/src/Repository/Settings/RepositorySettingsInterface.php @@ -14,10 +14,12 @@ interface RepositorySettingsInterface { const VA_GOV_CMS = 'va.gov-cms'; const CONTENT_BUILD = 'content-build'; const VETS_WEBSITE = 'vets-website'; + const NEXT_BUILD = 'next-build'; const REPOSITORY_NAMES = [ self::VA_GOV_CMS, self::CONTENT_BUILD, self::VETS_WEBSITE, + self::NEXT_BUILD, ]; // Settings keys for the repositories' filesystem paths. @@ -26,10 +28,13 @@ interface RepositorySettingsInterface { const VA_GOV_CMS_PATH_KEY = 'va_gov_app_root'; const CONTENT_BUILD_PATH_KEY = 'va_gov_web_root'; const VETS_WEBSITE_PATH_KEY = 'va_gov_vets_website_root'; + const NEXT_BUILD_PATH_KEY = 'va_gov_next_build_root'; + const PATH_KEYS = [ self::VA_GOV_CMS => self::VA_GOV_CMS_PATH_KEY, self::CONTENT_BUILD => self::CONTENT_BUILD_PATH_KEY, self::VETS_WEBSITE => self::VETS_WEBSITE_PATH_KEY, + self::NEXT_BUILD => self::NEXT_BUILD_PATH_KEY, ]; /** diff --git a/docroot/modules/custom/va_gov_git/va_gov_git.services.yml b/docroot/modules/custom/va_gov_git/va_gov_git.services.yml index bd4757fffe..3d02a62691 100644 --- a/docroot/modules/custom/va_gov_git/va_gov_git.services.yml +++ b/docroot/modules/custom/va_gov_git/va_gov_git.services.yml @@ -32,4 +32,8 @@ services: class: Drupal\va_gov_git\BranchSearch\BranchSearch factory: ['@va_gov_git.branch_search_factory', 'getVetsWebsite'] arguments: [] + va_gov_git.branch_search.next_build: + class: Drupal\va_gov_git\BranchSearch\BranchSearch + factory: [ '@va_gov_git.branch_search_factory', 'getNextBuild' ] + arguments: [] diff --git a/docroot/modules/custom/va_gov_github/src/Api/Client/Factory/ApiClientFactory.php b/docroot/modules/custom/va_gov_github/src/Api/Client/Factory/ApiClientFactory.php index f4f59cc7ed..5244d41b29 100644 --- a/docroot/modules/custom/va_gov_github/src/Api/Client/Factory/ApiClientFactory.php +++ b/docroot/modules/custom/va_gov_github/src/Api/Client/Factory/ApiClientFactory.php @@ -18,6 +18,7 @@ class ApiClientFactory implements ApiClientFactoryInterface { const VA_GOV_CMS = 'va.gov-cms'; const CONTENT_BUILD = 'content-build'; const VETS_WEBSITE = 'vets-website'; + const NEXT_BUILD = 'next-build'; /** * The settings service. @@ -64,4 +65,12 @@ public function getVetsWebsite(): ApiClientInterface { return $this->get(static::OWNER, static::VETS_WEBSITE, $this->settings->getApiToken()); } + /** + * {@inheritDoc} + */ + public function getNextBuild(): ApiClientInterface { + return $this->get(static::OWNER, static::NEXT_BUILD, + $this->settings->getApiToken()); + } + } diff --git a/docroot/modules/custom/va_gov_github/src/Api/Client/Factory/ApiClientFactoryInterface.php b/docroot/modules/custom/va_gov_github/src/Api/Client/Factory/ApiClientFactoryInterface.php index 4dbd126985..e97d8c7ae0 100644 --- a/docroot/modules/custom/va_gov_github/src/Api/Client/Factory/ApiClientFactoryInterface.php +++ b/docroot/modules/custom/va_gov_github/src/Api/Client/Factory/ApiClientFactoryInterface.php @@ -67,4 +67,15 @@ public function getContentBuild(): ApiClientInterface; */ public function getVetsWebsite(): ApiClientInterface; + /** + * Retrieve an API client for the Next Build repository. + * + * @return \Drupal\va_gov_github\Api\Client\ApiClientInterface + * The GitHub Api Client instance. + * + * @throws \Drupal\va_gov_github\Exception\InvalidApiTokenException + * If the GitHub API token is provided, but is invalid. + */ + public function getNextBuild(): ApiClientInterface; + } diff --git a/docroot/modules/custom/va_gov_github/va_gov_github.services.yml b/docroot/modules/custom/va_gov_github/va_gov_github.services.yml index 60aa99976c..f881f35ab4 100644 --- a/docroot/modules/custom/va_gov_github/va_gov_github.services.yml +++ b/docroot/modules/custom/va_gov_github/va_gov_github.services.yml @@ -17,3 +17,7 @@ services: class: Drupal\va_gov_github\Api\Client\ApiClientInterface factory: ['@va_gov_github.api_client_factory', 'getVetsWebsite'] arguments: [] + va_gov_github.api_client.next_build: + class: Drupal\va_gov_github\Api\Client\ApiClientInterface + factory: [ '@va_gov_github.api_client_factory', 'getNextBuild' ] + arguments: [] diff --git a/docroot/sites/default/settings.php b/docroot/sites/default/settings.php index b22fa5300e..99271a5dd2 100755 --- a/docroot/sites/default/settings.php +++ b/docroot/sites/default/settings.php @@ -126,6 +126,7 @@ $settings['va_gov_web_root'] = '/var/www/cms/web'; $settings['va_gov_app_root'] = '/var/www/cms'; $settings['va_gov_vets_website_root'] = '/var/www/cms/docroot/vendor/va-gov/vets-website'; +$settings['va_gov_next_build_root'] = '../next'; // Defaults (should only be local that doesn't set these), default to dev for config_split $config['config_split.config_split.dev']['status'] = TRUE; diff --git a/docroot/sites/default/settings/settings.tugboat.php b/docroot/sites/default/settings/settings.tugboat.php index 5bcd14b1e3..c6ab9b0c63 100644 --- a/docroot/sites/default/settings/settings.tugboat.php +++ b/docroot/sites/default/settings/settings.tugboat.php @@ -64,6 +64,7 @@ $settings['va_gov_app_root'] = getenv('TUGBOAT_ROOT'); $settings['va_gov_web_root'] = getenv('TUGBOAT_ROOT') . '/web'; $settings['va_gov_vets_website_root'] = getenv('TUGBOAT_ROOT') . '/docroot/vendor/va-gov/vets-website'; +$settings['va_gov_next_build_root'] = getenv('TUGBOAT_ROOT') . '/next'; $settings['memcache']['servers'] = [ 'memcache:11211' => 'default', diff --git a/scripts/next-build-frontend.sh b/scripts/next-build-frontend.sh new file mode 100644 index 0000000000..c6e32d1d4a --- /dev/null +++ b/scripts/next-build-frontend.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash + +# Exit if a command fails with a non-zero status code. +set -ex + +# Find repo root -> $reporoot +reporoot="unknown" +if [ ! -z "$IS_DDEV_PROJECT" ]; then + reporoot="/var/www/html" +fi +if [ ! -z "$TUGBOAT_ROOT" ]; then + reporoot="$TUGBOAT_ROOT" +fi +if [ "$reporoot" == "unknown" ]; then + echo "[!] Could not determine the environment type. Aborting!" + exit 1 +fi + +# For convenience. +cd $reporoot + +# Store path to site default files directory. +filesdir="${reporoot}/docroot/sites/default/files" + +# We really only want one build running at a time on any given environment. +if [ -f "${filesdir}/next-buildlock.txt" ]; then + echo "[!] There is already a build in progress. Aborting!" + exit 1 +fi +touch ${filesdir}/next-buildlock.txt + +# Make sure we clean up the build lock file if an error occurs or the build is killed. +trap "rm -f ${filesdir}/next-buildlock.txt && rm -f ${filesdir}/next-buildrequest.txt" INT TERM EXIT + +# Just because the path is really long: +logfile="${filesdir}/next-build.txt" + +# The currently selected version of next-build (may be "__default", a PR#, or a git ref) +next_build_version=$(drush va-gov-content-release:frontend-version:get next_build | tail -1) + +# The currently selected version of vets-website (may be "__default", a PR#, or a git ref) +vets_website_version=$(drush va-gov-content-release:frontend-version:get vets_website | tail -1) + +# Create a fresh log file. +[ -f "${logfile}" ] && rm ${logfile} +touch ${logfile} + +date >> ${logfile} + +echo "next-build version: ${next_build_version}" >> ${logfile} +echo "vets-website version: ${vets_website_version}" >> ${logfile} + +# Tell the frontend (and the user) that we're starting. +#drush va-gov:content-release:advance-state starting +echo "==> Starting a frontend build. This file will be updated as the build progresses." >> ${logfile} + +# Reset the repos to defaults. +#echo "==> Resetting VA repos to default versions" >> ${logfile} +#rm -rf ${reporoot}/docroot/vendor/va-gov +#composer install --no-scripts &>> ${logfile} + +# Get the requested next-build version +if [ "${next_build_version}" != "__default" ]; then + echo "==> Checking out the requested frontend version" >> ${logfile} + pushd ${reporoot}/next + if echo "${next_build_version}" | grep -qE '^[0-9]+$' > /dev/null; then + echo "==> Checking out PR #${next_build_version}" + git fetch origin pull/${next_build_version}/head &>> ${logfile} + else + echo "==> Checking out git ref ${next_build_version}" + git fetch origin ${next_build_version} &>> ${logfile} + fi + git checkout FETCH_HEAD &>> ${logfile} + popd +else + echo "==> Using default next-build version" >> ${logfile} +fi + +# Install 3rd party deps. +echo "==> Installing yarn dependencies" >> ${logfile} +composer va:next:install &>> ${logfile} + +# Get the requested vets-website version +if [ "${vets_website_version}" != "__default" ]; then + echo "==> Checking out the requested vets-website version" >> ${logfile} + pushd ${reporoot}/docroot/vendor/va-gov/vets-website + if echo "$vets_website_version" | grep -qE '^[0-9]+$' > /dev/null; then + echo "==> Checking out PR #${vets_website_version}" + git fetch origin pull/${vets_website_version}/head &>> ${logfile} + else + echo "==> Checking out git ref ${vets_website_version}" + git fetch origin ${vets_website_version} &>> ${logfile} + fi + git checkout FETCH_HEAD &>> ${logfile} + popd +else + echo "==> Using default vets-website version" >> ${logfile} +fi + +# Run the build. +echo "==> Starting build" >> ${logfile} +#drush va-gov:content-release:advance-state inprogress +composer va:next:build &>> ${logfile} + +# Advance the state in the frontend so another build can start. +echo "==> Build complete" >> ${logfile} +#drush va-gov:content-release:advance-state complete +#drush va-gov:content-release:advance-state ready + +# After this point, we are less concerned with errors; the build has completed. +set +e + +# Switch to the docroot to run drush commands. +cd "${reporoot}/docroot" + +# Log the timestamp of the build for reporting purposes. +drush state:set next_build.status.last_build_date "$(date)" + +# Just in case it wasn't clear :) +echo "==> Done" >> ${logfile} diff --git a/scripts/next-build.sh b/scripts/next-build.sh index ff134cf6fe..f322083dbf 100755 --- a/scripts/next-build.sh +++ b/scripts/next-build.sh @@ -1,11 +1,20 @@ #!/usr/bin/env bash #preview +ROOT=${TUGBOAT_ROOT:-${DDEV_APPROOT:-/var/www/html}} +if [ -n "${IS_DDEV_PROJECT}" ]; then + APP_ENV="local" +elif [ -n "${TUGBOAT_ROOT}" ]; then + APP_ENV="tugboat" +else + APP_ENV="tugboat" +fi + export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" source ~/.bashrc -cd next +cd "${ROOT}/next" -APP_ENV=tugboat yarn build:preview \ No newline at end of file +APP_ENV=${APP_ENV} yarn build:preview diff --git a/scripts/next-install.sh b/scripts/next-install.sh index e7063b52c2..11f3ba5c85 100755 --- a/scripts/next-install.sh +++ b/scripts/next-install.sh @@ -8,7 +8,10 @@ source ~/.bashrc # Installs the content-build dependencies. if [ ! -d next ]; then - git clone --single-branch --depth 1 https://github.com/department-of-veterans-affairs/next-build.git next + # Clone full so git information is available for content release form. + # I don't think this should be necessary, but branch information was not + # available in the content release form until I pulled down all information. + git clone https://github.com/department-of-veterans-affairs/next-build.git next else echo "Repo next-build already cloned." fi @@ -17,10 +20,13 @@ cd next #repo_root="$(git rev-parse --show-toplevel)" #pushd "${repo_root}" > /dev/null -nvm install 18.17.0 +nvm install 18.17.0 nvm use 18.17.0 -corepack enable -corepack prepare yarn@stable --activate + +# These steps caused the build to fail for me so I disabled temporarily. +#corepack enable +#corepack prepare yarn@stable --activate + echo "Node $(node -v)" echo "NPM $(npm -v)" echo "Yarn $(yarn -v)" diff --git a/scripts/queue_runner/next_queue_runner.sh b/scripts/queue_runner/next_queue_runner.sh new file mode 100644 index 0000000000..b636f6c686 --- /dev/null +++ b/scripts/queue_runner/next_queue_runner.sh @@ -0,0 +1,6 @@ +#!/bin/bash -l + +ROOT=${TUGBOAT_ROOT:-${DDEV_APPROOT:-/var/www/html}} +cd "${ROOT}" +[ -f "./docroot/sites/default/files/next-buildrequest.txt" ] && ./scripts/next-build-frontend.sh +sleep 10s diff --git a/tests/phpunit/va_gov_git/functional/Repository/Settings/RepositorySettingsTest.php b/tests/phpunit/va_gov_git/functional/Repository/Settings/RepositorySettingsTest.php index b50b2de832..ba8fc95b2d 100644 --- a/tests/phpunit/va_gov_git/functional/Repository/Settings/RepositorySettingsTest.php +++ b/tests/phpunit/va_gov_git/functional/Repository/Settings/RepositorySettingsTest.php @@ -47,6 +47,7 @@ public function testGetPathKey() { $this->assertEquals(RepositorySettingsInterface::PATH_KEYS['va.gov-cms'], $repositorySettings->getPathKey('va.gov-cms')); $this->assertEquals(RepositorySettingsInterface::PATH_KEYS['content-build'], $repositorySettings->getPathKey('content-build')); $this->assertEquals(RepositorySettingsInterface::PATH_KEYS['vets-website'], $repositorySettings->getPathKey('vets-website')); + $this->assertEquals(RepositorySettingsInterface::PATH_KEYS['next-build'], $repositorySettings->getPathKey('next-build')); } /** @@ -69,6 +70,10 @@ public function testList() { 'name' => RepositorySettingsInterface::VETS_WEBSITE, 'path' => Settings::get('va_gov_vets_website_root'), ], + [ + 'name' => RepositorySettingsInterface::NEXT_BUILD, + 'path' => Settings::get('va_gov_next_build_root'), + ], ], $repositorySettings->list()); } diff --git a/tests/phpunit/va_gov_git/unit/Repository/Settings/RepositorySettingsTest.php b/tests/phpunit/va_gov_git/unit/Repository/Settings/RepositorySettingsTest.php index 20b5d104d3..b0b3813dc8 100644 --- a/tests/phpunit/va_gov_git/unit/Repository/Settings/RepositorySettingsTest.php +++ b/tests/phpunit/va_gov_git/unit/Repository/Settings/RepositorySettingsTest.php @@ -30,6 +30,7 @@ public function getRepositorySettings() { 'va_gov_app_root' => '/srv/cms', 'va_gov_web_root' => '/srv/web', 'va_gov_vets_website_root' => '/srv/vets-website', + 'va_gov_next_build_root' => '/srv/next', ]); return new RepositorySettings($settings); } @@ -68,6 +69,7 @@ public function getPathKeyDataProvider() { ['va.gov-cms', RepositorySettings::VA_GOV_CMS_PATH_KEY], ['content-build', RepositorySettings::CONTENT_BUILD_PATH_KEY], ['vets-website', RepositorySettings::VETS_WEBSITE_PATH_KEY], + ['next-build', RepositorySettings::NEXT_BUILD_PATH_KEY], ]; } @@ -97,6 +99,7 @@ public function getPathDataProvider() { ['va.gov-cms', '/srv/cms'], ['content-build', '/srv/web'], ['vets-website', '/srv/vets-website'], + ['next-build', '/srv/next'], ]; } @@ -146,6 +149,10 @@ public function testList() { 'name' => RepositorySettingsInterface::VETS_WEBSITE, 'path' => '/srv/vets-website', ], + [ + 'name' => RepositorySettingsInterface::NEXT_BUILD, + 'path' => '/srv/next', + ], ], $repositorySettings->list()); }