From d2033c4d8d6aa7eb9af0126409db87dc8c1bb3ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:32:58 +0000 Subject: [PATCH 1/6] Initial plan From 56ea2d56e59eccb6fbb63a2f9b6961910525fa26 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:37:43 +0000 Subject: [PATCH 2/6] Update bootstrap and autodeploy for GitHub Actions compatibility - Update BootstrapTrait to provide clearer instructions for GitHub Actions setup - Update DeploymentTrait::deployConfigAutodeploy to work with GitHub Actions secrets - Remove outdated Travis CI encryption logic - Remove template file handling that no longer exists - Update instructions to match current GH Actions workflow requirements - Update README.md to clarify automatic deployment setup - Note that bootstrap automatically updates githubProject variable - Clarify that GitHub Actions workflows are pre-configured - Streamline instructions to match new autodeploy output Co-authored-by: AronNovak <114076+AronNovak@users.noreply.github.com> --- README.md | 47 ++++++++++++--------- robo-components/BootstrapTrait.php | 22 +++++++--- robo-components/DeploymentTrait.php | 64 +++++++++-------------------- 3 files changed, 64 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index ea3ffc495..7eda55b68 100644 --- a/README.md +++ b/README.md @@ -383,35 +383,42 @@ assembles it from the Git log. ## Automatic Deployment to Pantheon -In order to deploy upon every merge automatically using GitHub Actions, you shall: +### Prerequisites -1. Initiate QA (`qa` branch) multidev environment for the given project. +The GitHub Actions workflows for automatic deployment are already configured in the repository. You just need to set up the necessary credentials. + +### Setup Steps + +In order to deploy upon every merge automatically using GitHub Actions: + +1. Ensure QA (`qa` branch) multidev environment exists for the given project. This is automatically created during bootstrap, or can be created manually. 1. Double-check if `./.ddev/providers/pantheon.yaml` contains the proper Pantheon project name. 1. Get a [Pantheon machine token](https://pantheon.io/docs/machine-tokens) (using a dummy new Pantheon user ideally, one user per project for the sake of security) 1. Get a GitHub Personal access token. It will be used to post a comment to GitHub to the relevant issue when a merged PR is deployed, so set the expiry date far in the future enough for this. -1. `ddev robo deploy:config-autodeploy [your terminus token] [your github token]` -1. `git commit -m "Deployment secrets and configuration"` -1. Add the public key in `pantheon-key.pub` to the newly created dummy [Pantheon user](https://pantheon.io/docs/ssh-keys) -1. Set up the following in your GitHub repository settings: - - **GitHub Secrets** (Settings → Secrets and variables → Actions → Secrets): - - `TERMINUS_TOKEN`: Your Pantheon machine token - - `PANTHEON_DEPLOY_KEY`: The SSH private key for deployment - - `GH_TOKEN`: GitHub personal access token for posting deployment comments - - **GitHub Variables** (Settings → Secrets and variables → Actions → Variables): - - `PANTHEON_GIT_URL`: The Pantheon Git URL for your project - - `ROLLBAR_SERVER_TOKEN`: Your Rollbar server token (optional) - - `DEPLOY_EXCLUDE_WARNING`: Warnings to exclude from deployment notifications (optional) +1. Run the autodeploy configuration command: + ```bash + ddev robo deploy:config-autodeploy [your terminus token] [your github token] + ``` -1. Actualize `public static string $githubProject = 'Gizra/the-client';` in the `RoboFile.php`. + This will generate an SSH key pair and provide instructions for setting up GitHub Secrets and Variables. + +1. Follow the instructions provided by the command to: + - Add the SSH public key (`pantheon-key.pub`) to your [Pantheon account](https://pantheon.io/docs/ssh-keys) + - Set up GitHub Secrets (TERMINUS_TOKEN, PANTHEON_DEPLOY_KEY, GH_TOKEN) + - Set up GitHub Variables (PANTHEON_GIT_URL, ROLLBAR_SERVER_TOKEN, DEPLOY_EXCLUDE_WARNING) + +**Note**: If you used the `bootstrap:project` command to create your project, the `$githubProject` variable in `DeploymentTrait.php` is automatically updated with your organization and project name. Otherwise, you'll need to manually update `public static string $githubProject = 'YourOrg/your-project';` in `robo-components/DeploymentTrait.php`. Optionally you can specify which target branch you'd like to push on Pantheon, by default it's `master`, so the target is the DEV environment, but alternatively you can issue: -`ddev robo deploy:config-autodeploy [your terminus token] [your github token] [pantheon project name] [gh_branch] [pantheon_branch]` +```bash +ddev robo deploy:config-autodeploy [your terminus token] [your github token] [github_deploy_branch] [pantheon_deploy_branch] +``` + +### Tag-based Deployments After you have automatic deployment for a project, you are able to deploy to Pantheon `test` and `live` using Git tags. -`git tag 0.1.2` will imply a deployment to the `test` environment (and `dev` - as enforced by Pantheon). -`git tag 0.1.2_live` will imply a deployment to `live`. In order to make it fast, you need to first create the tag that deploy to `test`, then you need to tag the same commit with a tag suffixed with `_live`. +- `git tag 0.1.2` will imply a deployment to the `test` environment (and `dev` - as enforced by Pantheon). +- `git tag 0.1.2_live` will imply a deployment to `live`. In order to make it fast, you need to first create the tag that deploy to `test`, then you need to tag the same commit with a tag suffixed with `_live`. ### Excluding Warnings in Deployment diff --git a/robo-components/BootstrapTrait.php b/robo-components/BootstrapTrait.php index 4565c1019..adda5da46 100644 --- a/robo-components/BootstrapTrait.php +++ b/robo-components/BootstrapTrait.php @@ -53,11 +53,23 @@ public function bootstrapProject(string $project_name, string $github_repository $this->taskExec("terminus secrets:set $project_machine_name.dev tfa $tfa_secret")->run(); $this->say("Bootstrap completed successfully."); - $this->say("You might want to run the following commands to properly place the project:"); - $this->say("mv .bootstrap ../$project_machine_name"); - $this->say("mv .pantheon ../$project_machine_name/.pantheon"); - $this->say("To configure autodeployment to Pantheon run:"); - $this->say("ddev robo deploy:config-autodeploy $terminus_token $github_token"); + $this->say(""); + $this->say("Next steps:"); + $this->say("1. Move the project to its final location:"); + $this->say(" mv .bootstrap ../$project_machine_name"); + $this->say(" mv .pantheon ../$project_machine_name/.pantheon"); + $this->say(""); + $this->say("2. Configure automatic deployment to Pantheon with GitHub Actions:"); + $this->say(" cd ../$project_machine_name"); + $this->say(" ddev robo deploy:config-autodeploy $terminus_token $github_token"); + $this->say(""); + $this->say(" This will generate SSH keys and provide instructions for:"); + $this->say(" - Setting up GitHub Secrets (TERMINUS_TOKEN, PANTHEON_DEPLOY_KEY, GH_TOKEN)"); + $this->say(" - Setting up GitHub Variables (PANTHEON_GIT_URL, ROLLBAR_SERVER_TOKEN)"); + $this->say(" - Adding the SSH public key to your Pantheon account"); + $this->say(""); + $this->say("For full deployment setup details, see:"); + $this->say("https://github.com/$organization/$project_machine_name#automatic-deployment-to-pantheon"); } /** diff --git a/robo-components/DeploymentTrait.php b/robo-components/DeploymentTrait.php index 00e7435d1..d9ea7f161 100644 --- a/robo-components/DeploymentTrait.php +++ b/robo-components/DeploymentTrait.php @@ -720,63 +720,39 @@ public function deployConfigAutodeploy(string $token, string $github_token, stri throw new \Exception('The key generation failed.'); } - // Encrypt the SSH key for use in GitHub Actions. - $result = $this->taskExec('openssl rand -hex 32')->printOutput(FALSE)->run(); - if ($result->getExitCode() !== 0) { - throw new \Exception('Failed to generate encryption key.'); - } - $encryption_key = trim($result->getMessage()); - - $result = $this->taskExec('openssl rand -hex 16')->printOutput(FALSE)->run(); - if ($result->getExitCode() !== 0) { - throw new \Exception('Failed to generate encryption IV.'); - } - $encryption_iv = trim($result->getMessage()); - - $result = $this->taskExec("openssl aes-256-cbc -K $encryption_key -iv $encryption_iv -in pantheon-key -out pantheon-key.enc")->run(); - if ($result->getExitCode() !== 0) { - throw new \Exception('The encryption of the private key failed.'); - } - $result = $this->taskExec("terminus connection:info $project_name.dev --fields='Git Command' --format=string | awk '{print $3}'") ->printOutput(FALSE) ->run(); $pantheon_git_url = trim($result->getMessage()); - // Update GitHub Actions workflows if they exist. - if (file_exists('.github/workflows/lint.template.yml')) { - $this->_exec("cp .github/workflows/lint.template.yml .github/workflows/lint.yml"); - $this->taskReplaceInFile('.github/workflows/lint.yml') - ->from('{{ GITHUB_DEPLOY_BRANCH }}') - ->to($github_deploy_branch) - ->run(); - } - - $result = $this->taskExec('git add pantheon-key.enc')->run(); - if ($result->getExitCode() !== 0) { - throw new \Exception("git add failed."); - } - $this->say("The project was prepared for automatic deployment to Pantheon using GitHub Actions"); $this->say(""); $this->say("Please complete the following steps:"); $this->say(""); - $this->say("1. Add the following secrets to your GitHub repository:"); - $this->say(" - Go to: Settings → Secrets and variables → Actions → New repository secret"); - $this->say(" - PANTHEON_GIT_URL: " . $pantheon_git_url); - $this->say(" - TERMINUS_TOKEN: " . $token); - $this->say(" - ENCRYPTED_KEY: " . $encryption_key); - $this->say(" - ENCRYPTED_IV: " . $encryption_iv); - $this->say(" - GITHUB_TOKEN: (use the automatically provided token or your personal token)"); - $this->say(" - ROLLBAR_SERVER_TOKEN: (your Rollbar token if applicable)"); - $this->say(""); - $this->say("2. Add the SSH public key to the Pantheon account:"); + $this->say("1. Add the SSH public key to the Pantheon account:"); $this->say(" - Key location: pantheon-key.pub"); $this->say(" - Instructions: https://pantheon.io/docs/ssh-keys"); $this->say(""); - $this->say("3. Review and commit the encrypted key file (pantheon-key.enc)"); + $this->say("2. Set up the following GitHub Secrets:"); + $this->say(" - Go to: Settings → Secrets and variables → Actions → Secrets → New repository secret"); + $this->say(" - TERMINUS_TOKEN: " . $token); + $this->say(" - PANTHEON_DEPLOY_KEY: (paste the contents of pantheon-key file)"); + $this->say(" - GH_TOKEN: " . $github_token); + $this->say(""); + $this->say("3. Set up the following GitHub Variables:"); + $this->say(" - Go to: Settings → Secrets and variables → Actions → Variables → New repository variable"); + $this->say(" - PANTHEON_GIT_URL: " . $pantheon_git_url); + $this->say(" - ROLLBAR_SERVER_TOKEN: (your Rollbar token, optional)"); + $this->say(" - DEPLOY_EXCLUDE_WARNING: (warnings to exclude, optional)"); + $this->say(""); + $this->say("4. IMPORTANT: Keep the pantheon-key file secure and DO NOT commit it to the repository."); + $this->say(" You only need to add its contents as the PANTHEON_DEPLOY_KEY secret in GitHub."); + $this->say(""); + $this->say("5. After setting up secrets and variables, you can delete the pantheon-key files locally."); + $this->say(""); + $this->say("6. Ensure nested docroot is configured: https://pantheon.io/docs/nested-docroot"); $this->say(""); - $this->say("4. Ensure nested docroot is configured: https://pantheon.io/docs/nested-docroot"); + $this->say("For more details, see the 'Automatic Deployment to Pantheon' section in README.md"); } /** From 01aedba9d3018599322c2148c25a4c84431347a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:41:14 +0000 Subject: [PATCH 3/6] Address code review feedback - Fix undefined $organization variable in BootstrapTrait (should be $github_organization) - Remove unused parameters from deployConfigAutodeploy function - Update docblock to reflect actual function behavior - Remove misleading documentation about branch parameters in README Co-authored-by: AronNovak <114076+AronNovak@users.noreply.github.com> --- README.md | 5 ----- robo-components/BootstrapTrait.php | 2 +- robo-components/DeploymentTrait.php | 15 ++++++--------- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7eda55b68..6b28c5d7c 100644 --- a/README.md +++ b/README.md @@ -409,11 +409,6 @@ In order to deploy upon every merge automatically using GitHub Actions: **Note**: If you used the `bootstrap:project` command to create your project, the `$githubProject` variable in `DeploymentTrait.php` is automatically updated with your organization and project name. Otherwise, you'll need to manually update `public static string $githubProject = 'YourOrg/your-project';` in `robo-components/DeploymentTrait.php`. -Optionally you can specify which target branch you'd like to push on Pantheon, by default it's `master`, so the target is the DEV environment, but alternatively you can issue: -```bash -ddev robo deploy:config-autodeploy [your terminus token] [your github token] [github_deploy_branch] [pantheon_deploy_branch] -``` - ### Tag-based Deployments After you have automatic deployment for a project, you are able to deploy to Pantheon `test` and `live` using Git tags. diff --git a/robo-components/BootstrapTrait.php b/robo-components/BootstrapTrait.php index adda5da46..4b25a53e3 100644 --- a/robo-components/BootstrapTrait.php +++ b/robo-components/BootstrapTrait.php @@ -69,7 +69,7 @@ public function bootstrapProject(string $project_name, string $github_repository $this->say(" - Adding the SSH public key to your Pantheon account"); $this->say(""); $this->say("For full deployment setup details, see:"); - $this->say("https://github.com/$organization/$project_machine_name#automatic-deployment-to-pantheon"); + $this->say("https://github.com/$github_organization/$project_machine_name#automatic-deployment-to-pantheon"); } /** diff --git a/robo-components/DeploymentTrait.php b/robo-components/DeploymentTrait.php index d9ea7f161..01d73bc1e 100644 --- a/robo-components/DeploymentTrait.php +++ b/robo-components/DeploymentTrait.php @@ -693,24 +693,21 @@ public function deployPantheonInstallEnv(string $env = 'qa', ?string $pantheon_n } /** - * Prepares the repository to perform automatic deployment to Pantheon. + * Configures automatic deployment to Pantheon using GitHub Actions. + * + * This command generates an SSH key pair and provides instructions for + * setting up the necessary GitHub Secrets and Variables for automatic + * deployment. * * @param string $token * Terminus machine token: https://pantheon.io/docs/machine-tokens. * @param string $github_token * Personal GitHub token: * https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token. - * @param string $github_deploy_branch - * The branch that should be pushed automatically to Pantheon. By default, - * it's 'main', the default GitHub branch for any new project. - * @param string $pantheon_deploy_branch - * The branch at the artifact repo that should be the target of the - * deployment. As we typically deploy to QA, the default value here is 'qa', - * that multi-dev environment should be created by hand beforehand. * * @throws \Exception */ - public function deployConfigAutodeploy(string $token, string $github_token, string $github_deploy_branch = 'main', string $pantheon_deploy_branch = 'qa'): void { + public function deployConfigAutodeploy(string $token, string $github_token): void { $pantheon_info = $this->getPantheonNameAndEnv(); $project_name = $pantheon_info['name']; From 82479daa5b12afd9a7f38430285ae4e7f539a000 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:48:03 +0000 Subject: [PATCH 4/6] Add gh CLI automation for GitHub Secrets/Variables setup - Detect if gh CLI is available and offer to automate setup - Automatically set TERMINUS_TOKEN, PANTHEON_DEPLOY_KEY, GH_TOKEN secrets - Automatically set PANTHEON_GIT_URL variable - Fall back to manual instructions if gh CLI not available or user declines - Add printManualInstructions() helper method - Update README to mention automated setup option Co-authored-by: AronNovak <114076+AronNovak@users.noreply.github.com> --- README.md | 6 +- robo-components/DeploymentTrait.php | 92 +++++++++++++++++++++++++---- 2 files changed, 83 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 6b28c5d7c..c37399ac4 100644 --- a/README.md +++ b/README.md @@ -400,12 +400,12 @@ In order to deploy upon every merge automatically using GitHub Actions: ddev robo deploy:config-autodeploy [your terminus token] [your github token] ``` - This will generate an SSH key pair and provide instructions for setting up GitHub Secrets and Variables. + This will generate an SSH key pair. If you have the [GitHub CLI](https://cli.github.com/) (`gh`) installed, the command can automatically set up GitHub Secrets and Variables for you. Otherwise, it will provide manual instructions. 1. Follow the instructions provided by the command to: - Add the SSH public key (`pantheon-key.pub`) to your [Pantheon account](https://pantheon.io/docs/ssh-keys) - - Set up GitHub Secrets (TERMINUS_TOKEN, PANTHEON_DEPLOY_KEY, GH_TOKEN) - - Set up GitHub Variables (PANTHEON_GIT_URL, ROLLBAR_SERVER_TOKEN, DEPLOY_EXCLUDE_WARNING) + - If using automated setup: Confirm when prompted to automatically configure GitHub Secrets and Variables + - If setting up manually: Configure GitHub Secrets (TERMINUS_TOKEN, PANTHEON_DEPLOY_KEY, GH_TOKEN) and Variables (PANTHEON_GIT_URL, ROLLBAR_SERVER_TOKEN, DEPLOY_EXCLUDE_WARNING) as instructed **Note**: If you used the `bootstrap:project` command to create your project, the `$githubProject` variable in `DeploymentTrait.php` is automatically updated with your organization and project name. Otherwise, you'll need to manually update `public static string $githubProject = 'YourOrg/your-project';` in `robo-components/DeploymentTrait.php`. diff --git a/robo-components/DeploymentTrait.php b/robo-components/DeploymentTrait.php index 01d73bc1e..84d4ac426 100644 --- a/robo-components/DeploymentTrait.php +++ b/robo-components/DeploymentTrait.php @@ -2,6 +2,7 @@ namespace RoboComponents; +use Robo\Symfony\ConsoleIO; use Symfony\Component\Yaml\Yaml; /** @@ -724,32 +725,99 @@ public function deployConfigAutodeploy(string $token, string $github_token): voi $this->say("The project was prepared for automatic deployment to Pantheon using GitHub Actions"); $this->say(""); - $this->say("Please complete the following steps:"); + + // Check if gh CLI is available. + $gh_available = $this->taskExec('which gh') + ->printOutput(FALSE) + ->run() + ->wasSuccessful(); + + if ($gh_available) { + $this->say("Detected gh CLI - GitHub Secrets and Variables can be set automatically."); + $io = new ConsoleIO($this->input(), $this->output()); + $automate = $io->confirm('Would you like to automatically set up GitHub Secrets and Variables now?', TRUE); + + if ($automate) { + $this->say(""); + $this->say("Setting up GitHub Secrets and Variables..."); + + // Set secrets. + $result = $this->taskExec("gh secret set TERMINUS_TOKEN --body '$token' --repo " . self::$githubProject)->run(); + if ($result->wasSuccessful()) { + $this->say("✓ Set TERMINUS_TOKEN secret"); + } + + $result = $this->taskExec("gh secret set PANTHEON_DEPLOY_KEY < pantheon-key --repo " . self::$githubProject)->run(); + if ($result->wasSuccessful()) { + $this->say("✓ Set PANTHEON_DEPLOY_KEY secret"); + } + + $result = $this->taskExec("gh secret set GH_TOKEN --body '$github_token' --repo " . self::$githubProject)->run(); + if ($result->wasSuccessful()) { + $this->say("✓ Set GH_TOKEN secret"); + } + + // Set variables. + $result = $this->taskExec("gh variable set PANTHEON_GIT_URL --body '$pantheon_git_url' --repo " . self::$githubProject)->run(); + if ($result->wasSuccessful()) { + $this->say("✓ Set PANTHEON_GIT_URL variable"); + } + + $this->say(""); + $this->say("GitHub Secrets and Variables have been configured!"); + $this->say(""); + $this->say("Optional: You can also set ROLLBAR_SERVER_TOKEN and DEPLOY_EXCLUDE_WARNING variables if needed:"); + $this->say(" gh variable set ROLLBAR_SERVER_TOKEN --body 'your-token' --repo " . self::$githubProject); + $this->say(" gh variable set DEPLOY_EXCLUDE_WARNING --body 'warning1|warning2' --repo " . self::$githubProject); + } + else { + $this->printManualInstructions($token, $github_token, $pantheon_git_url); + } + } + else { + $this->say("Note: Install gh CLI (https://cli.github.com/) to automate secret/variable setup."); + $this->say(""); + $this->printManualInstructions($token, $github_token, $pantheon_git_url); + } + $this->say(""); + $this->say("Remaining steps:"); $this->say("1. Add the SSH public key to the Pantheon account:"); $this->say(" - Key location: pantheon-key.pub"); $this->say(" - Instructions: https://pantheon.io/docs/ssh-keys"); $this->say(""); - $this->say("2. Set up the following GitHub Secrets:"); + $this->say("2. IMPORTANT: Keep the pantheon-key file secure and DO NOT commit it to the repository."); + $this->say(" After adding the public key to Pantheon, you can delete the pantheon-key files locally."); + $this->say(""); + $this->say("3. Ensure nested docroot is configured: https://pantheon.io/docs/nested-docroot"); + $this->say(""); + $this->say("For more details, see the 'Automatic Deployment to Pantheon' section in README.md"); + } + + /** + * Prints manual instructions for setting up GitHub Secrets and Variables. + * + * @param string $token + * The Terminus token. + * @param string $github_token + * The GitHub token. + * @param string $pantheon_git_url + * The Pantheon Git URL. + */ + protected function printManualInstructions(string $token, string $github_token, string $pantheon_git_url): void { + $this->say("Please complete the following steps manually:"); + $this->say(""); + $this->say("1. Set up the following GitHub Secrets:"); $this->say(" - Go to: Settings → Secrets and variables → Actions → Secrets → New repository secret"); $this->say(" - TERMINUS_TOKEN: " . $token); $this->say(" - PANTHEON_DEPLOY_KEY: (paste the contents of pantheon-key file)"); $this->say(" - GH_TOKEN: " . $github_token); $this->say(""); - $this->say("3. Set up the following GitHub Variables:"); + $this->say("2. Set up the following GitHub Variables:"); $this->say(" - Go to: Settings → Secrets and variables → Actions → Variables → New repository variable"); $this->say(" - PANTHEON_GIT_URL: " . $pantheon_git_url); $this->say(" - ROLLBAR_SERVER_TOKEN: (your Rollbar token, optional)"); $this->say(" - DEPLOY_EXCLUDE_WARNING: (warnings to exclude, optional)"); - $this->say(""); - $this->say("4. IMPORTANT: Keep the pantheon-key file secure and DO NOT commit it to the repository."); - $this->say(" You only need to add its contents as the PANTHEON_DEPLOY_KEY secret in GitHub."); - $this->say(""); - $this->say("5. After setting up secrets and variables, you can delete the pantheon-key files locally."); - $this->say(""); - $this->say("6. Ensure nested docroot is configured: https://pantheon.io/docs/nested-docroot"); - $this->say(""); - $this->say("For more details, see the 'Automatic Deployment to Pantheon' section in README.md"); } /** From 9dca072322b5d662a933c48501f048197624fc12 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:51:55 +0000 Subject: [PATCH 5/6] Auto-install gh CLI if not available in DDEV - Automatically install gh CLI using apt if not found - Use sudo to install packages in DDEV environment - Add quiet flags to apt commands to reduce output noise - Fall back to manual instructions if installation fails - Ensure remaining steps (SSH key setup) always show - Update README to reflect automatic installation Co-authored-by: AronNovak <114076+AronNovak@users.noreply.github.com> --- README.md | 2 +- robo-components/DeploymentTrait.php | 35 +++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c37399ac4..f026755bb 100644 --- a/README.md +++ b/README.md @@ -400,7 +400,7 @@ In order to deploy upon every merge automatically using GitHub Actions: ddev robo deploy:config-autodeploy [your terminus token] [your github token] ``` - This will generate an SSH key pair. If you have the [GitHub CLI](https://cli.github.com/) (`gh`) installed, the command can automatically set up GitHub Secrets and Variables for you. Otherwise, it will provide manual instructions. + This will generate an SSH key pair. The command will automatically install the [GitHub CLI](https://cli.github.com/) (`gh`) if it's not already available in your DDEV environment, then offer to automatically set up GitHub Secrets and Variables. If installation fails, it will provide manual instructions. 1. Follow the instructions provided by the command to: - Add the SSH public key (`pantheon-key.pub`) to your [Pantheon account](https://pantheon.io/docs/ssh-keys) diff --git a/robo-components/DeploymentTrait.php b/robo-components/DeploymentTrait.php index 84d4ac426..00f06b366 100644 --- a/robo-components/DeploymentTrait.php +++ b/robo-components/DeploymentTrait.php @@ -732,8 +732,40 @@ public function deployConfigAutodeploy(string $token, string $github_token): voi ->run() ->wasSuccessful(); + // If gh CLI is not available, try to install it. + if (!$gh_available) { + $this->say("GitHub CLI (gh) is not installed. Installing it now..."); + + // Install gh CLI on Ubuntu/Debian. + $install_commands = [ + 'sudo apt-get update -qq', + 'sudo apt-get install -y -qq curl', + 'curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg 2>/dev/null', + 'sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg', + 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null', + 'sudo apt-get update -qq', + 'sudo apt-get install -y -qq gh', + ]; + + $install_failed = FALSE; + foreach ($install_commands as $cmd) { + $result = $this->taskExec($cmd)->run(); + if (!$result->wasSuccessful()) { + $this->say("Warning: Failed to install gh CLI automatically."); + $install_failed = TRUE; + break; + } + } + + if (!$install_failed) { + $this->say("✓ GitHub CLI installed successfully!"); + $this->say(""); + $gh_available = TRUE; + } + } + if ($gh_available) { - $this->say("Detected gh CLI - GitHub Secrets and Variables can be set automatically."); + $this->say("GitHub Secrets and Variables can be set automatically using gh CLI."); $io = new ConsoleIO($this->input(), $this->output()); $automate = $io->confirm('Would you like to automatically set up GitHub Secrets and Variables now?', TRUE); @@ -775,7 +807,6 @@ public function deployConfigAutodeploy(string $token, string $github_token): voi } } else { - $this->say("Note: Install gh CLI (https://cli.github.com/) to automate secret/variable setup."); $this->say(""); $this->printManualInstructions($token, $github_token, $pantheon_git_url); } From 58d204d2b75978d1ae799626a4c6bd7def8e31b1 Mon Sep 17 00:00:00 2001 From: Aron Novak Date: Sat, 31 Jan 2026 06:13:12 +0100 Subject: [PATCH 6/6] fixes on the bootstrap project --- robo-components/BootstrapTrait.php | 132 +++++++++++++++++------------ 1 file changed, 78 insertions(+), 54 deletions(-) diff --git a/robo-components/BootstrapTrait.php b/robo-components/BootstrapTrait.php index 4b25a53e3..16f5f3ca3 100644 --- a/robo-components/BootstrapTrait.php +++ b/robo-components/BootstrapTrait.php @@ -13,7 +13,7 @@ trait BootstrapTrait { * Bootstrap a new client project on Pantheon.io. * * @param string $project_name - * The project name. + * The Pantheon project name. * @param string $github_repository_url * The clone URL of the GitHub repository. * @param string $terminus_token @@ -27,40 +27,39 @@ trait BootstrapTrait { * The HTTP basic auth password. Optional. */ public function bootstrapProject(string $project_name, string $github_repository_url, string $terminus_token, string $github_token, string $http_basic_auth_user = '', string $http_basic_auth_password = '') { - // Extract project name from $github_repository_url. - // The syntax is like: git@github.com:Organization/projectname.git . + // Extract organization and repo name from GitHub URL. preg_match('/github.com[:\/](.*)\/(.*)\.git/', $github_repository_url, $matches); $github_organization = $matches[1]; - $project_machine_name = $matches[2]; + $github_repo_name = $matches[2]; - $this->verifyRequirements($project_name, $github_organization, $project_machine_name, $terminus_token, $github_token, $http_basic_auth_user, $http_basic_auth_password); + $this->verifyRequirements($project_name, $github_organization, $github_repo_name, $terminus_token, $github_token, $http_basic_auth_user, $http_basic_auth_password); - $this->prepareGithubRepository($project_name, $github_organization, $project_machine_name, $github_repository_url); + $this->prepareGithubRepository($project_name, $github_organization, $github_repo_name, $github_repository_url); - $this->createPantheonProject($terminus_token, $project_name, $project_machine_name); + $this->createPantheonProject($terminus_token, $project_name); - $this->deployPantheonInstallEnv('dev', $project_machine_name); - $this->deployPantheonInstallEnv('qa', $project_machine_name); + $this->deployPantheonInstallEnv('dev', $project_name); + $this->deployPantheonInstallEnv('qa', $project_name); - $this->lockPantheonEnvironments($project_machine_name, $http_basic_auth_user, $http_basic_auth_password); + $this->lockPantheonEnvironments($project_name, $http_basic_auth_user, $http_basic_auth_password); $tfa_secret = $this->taskExec("openssl rand -base64 32") ->printOutput(FALSE) ->run() ->getMessage(); $this->taskExec('terminus self:plugin:install pantheon-systems/terminus-secrets-plugin')->run(); - $this->taskExec("terminus secrets:set $project_machine_name.qa tfa $tfa_secret")->run(); - $this->taskExec("terminus secrets:set $project_machine_name.dev tfa $tfa_secret")->run(); + $this->taskExec("terminus secrets:set $project_name.qa tfa $tfa_secret")->run(); + $this->taskExec("terminus secrets:set $project_name.dev tfa $tfa_secret")->run(); $this->say("Bootstrap completed successfully."); $this->say(""); $this->say("Next steps:"); $this->say("1. Move the project to its final location:"); - $this->say(" mv .bootstrap ../$project_machine_name"); - $this->say(" mv .pantheon ../$project_machine_name/.pantheon"); + $this->say(" mv .bootstrap ../$github_repo_name"); + $this->say(" mv .pantheon ../$github_repo_name/.pantheon"); $this->say(""); $this->say("2. Configure automatic deployment to Pantheon with GitHub Actions:"); - $this->say(" cd ../$project_machine_name"); + $this->say(" cd ../$github_repo_name"); $this->say(" ddev robo deploy:config-autodeploy $terminus_token $github_token"); $this->say(""); $this->say(" This will generate SSH keys and provide instructions for:"); @@ -69,22 +68,22 @@ public function bootstrapProject(string $project_name, string $github_repository $this->say(" - Adding the SSH public key to your Pantheon account"); $this->say(""); $this->say("For full deployment setup details, see:"); - $this->say("https://github.com/$github_organization/$project_machine_name#automatic-deployment-to-pantheon"); + $this->say("https://github.com/$github_organization/$github_repo_name#automatic-deployment-to-pantheon"); } /** * Prepares the new GitHub repository for the project. * * @param string $project_name - * The project name. - * @param string $organization + * The Pantheon site name. + * @param string $github_organization * The GitHub organization. - * @param string $project_machine_name - * The project machine name in GH slug. + * @param string $github_repo_name + * The GitHub repository name. * @param string $github_repository_url * The clone URL of the GitHub repository. */ - protected function prepareGithubRepository(string $project_name, string $organization, string $project_machine_name, string $github_repository_url) { + protected function prepareGithubRepository(string $project_name, string $github_organization, string $github_repo_name, string $github_repository_url) { $temp_remote = 'bootstrap_' . time(); $this->taskExec("git remote add $temp_remote $github_repository_url") ->run(); @@ -106,12 +105,12 @@ protected function prepareGithubRepository(string $project_name, string $organiz $this->taskReplaceInFile('.bootstrap/robo-components/DeploymentTrait.php') ->from('Gizra/drupal-starter') - ->to("$organization/$project_machine_name") + ->to("$github_organization/$github_repo_name") ->run(); $this->taskReplaceInFile('.bootstrap/.ddev/config.yaml') ->from('drupal-starter') - ->to($project_machine_name) + ->to($github_repo_name) ->run(); $this->taskReplaceInFile('.bootstrap/.ddev/config.yaml') @@ -136,12 +135,12 @@ protected function prepareGithubRepository(string $project_name, string $organiz $this->taskReplaceInFile('.bootstrap/README.md') ->from('Gizra') - ->to($organization) + ->to($github_organization) ->run(); $this->taskReplaceInFile('.bootstrap/README.md') ->from('drupal-starter') - ->to($project_machine_name) + ->to($github_repo_name) ->run(); $this->taskReplaceInFile('.bootstrap/.ddev/providers/pantheon.yaml') @@ -151,18 +150,28 @@ protected function prepareGithubRepository(string $project_name, string $organiz $this->taskReplaceInFile('.bootstrap/composer.json') ->from('drupal-starter') - ->to(strtolower($project_machine_name)) + ->to(strtolower($github_repo_name)) ->run(); $this->taskReplaceInFile('.bootstrap/composer.json') ->from('gizra') - ->to(strtolower($organization)) + ->to(strtolower($github_organization)) ->run(); $this->taskReplaceInFile('.bootstrap/web/sites/default/settings.pantheon.php') ->from('drupal_starter') - ->to(str_replace('-', '_', $project_machine_name)) + ->to(str_replace('-', '_', $github_repo_name)) ->run(); + // Run composer install first to get contrib modules (needed for + // merge-plugin to find webform's composer.libraries.json). + $result = $this->taskExec("cd .bootstrap && composer install --no-interaction") + ->run() + ->getExitCode(); + if ($result !== 0) { + throw new \Exception("Failed to run composer install in GH repository."); + } + + // Now update the lock file hash after the project name replacements. $result = $this->taskExec("cd .bootstrap && composer update --lock") ->run() ->getExitCode(); @@ -190,11 +199,9 @@ protected function prepareGithubRepository(string $project_name, string $organiz * @param string $terminus_token * The Pantheon machine token. * @param string $project_name - * The project name. - * @param string $project_machine_name - * The project machine name in GH slug. + * The Pantheon site name. */ - public function createPantheonProject(string $terminus_token, string $project_name, string $project_machine_name) { + public function createPantheonProject(string $terminus_token, string $project_name) { $result = $this->taskExec("terminus auth:login --machine-token=\"$terminus_token\"") ->run() ->getExitCode(); @@ -225,7 +232,7 @@ public function createPantheonProject(string $terminus_token, string $project_na // matches Drupal Starter. $upstream_id = "bde48795-b16d-443f-af01-8b1790caa1af"; - $result = $this->taskExec("terminus site:create $project_machine_name \"$project_name\" \"$upstream_id\" --org=\"$selected_organization_id\"") + $result = $this->taskExec("terminus site:create $project_name \"$project_name\" \"$upstream_id\" --org=\"$selected_organization_id\"") ->run() ->getExitCode(); @@ -233,7 +240,7 @@ public function createPantheonProject(string $terminus_token, string $project_na throw new \Exception("Failed to create the Pantheon project."); } - $result = $this->taskExec("terminus connection:set $project_machine_name.dev git") + $result = $this->taskExec("terminus connection:set $project_name.dev git") ->run() ->getExitCode(); @@ -243,7 +250,7 @@ public function createPantheonProject(string $terminus_token, string $project_na // Retrieve Git repository from Pantheon, then clone the artifact repository // to .pantheon directory. - $pantheon_repository_url = $this->taskExec("terminus connection:info $project_machine_name.dev --field=git_url") + $pantheon_repository_url = $this->taskExec("terminus connection:info $project_name.dev --field=git_url") ->printOutput(FALSE) ->run() ->getMessage(); @@ -309,7 +316,7 @@ public function createPantheonProject(string $terminus_token, string $project_na } // Create QA environment on Pantheon. - $result = $this->taskExec("terminus multidev:create $project_machine_name.dev qa") + $result = $this->taskExec("terminus multidev:create $project_name.dev qa") ->run() ->getExitCode(); @@ -317,7 +324,7 @@ public function createPantheonProject(string $terminus_token, string $project_na throw new \Exception('Failed to create the Pantheon QA environment.'); } - $result = $this->taskExec("terminus connection:set $project_machine_name.qa git") + $result = $this->taskExec("terminus connection:set $project_name.qa git") ->run() ->getExitCode(); @@ -329,33 +336,33 @@ public function createPantheonProject(string $terminus_token, string $project_na /** * Lock all Pantheon environments for the given site. * - * @param string $project_machine_name - * The machine name of the project. + * @param string $project_name + * The Pantheon site name. * @param string $http_basic_auth_user * The HTTP basic auth user. * @param string $http_basic_auth_password * The HTTP basic auth password. */ - public function lockPantheonEnvironments(string $project_machine_name, string $http_basic_auth_user, string $http_basic_auth_password) { + public function lockPantheonEnvironments(string $project_name, string $http_basic_auth_user, string $http_basic_auth_password) { if (empty($http_basic_auth_user) || empty($http_basic_auth_password)) { $this->say("No HTTP basic auth credentials were provided. Pantheon environments will not be locked."); return; } - $pantheon_environments = $this->taskExec("terminus env:list $project_machine_name --field=ID --format=list") + $pantheon_environments = $this->taskExec("terminus env:list $project_name --field=ID --format=list") ->printOutput(FALSE) ->run() ->getMessage(); $pantheon_environments = explode(PHP_EOL, $pantheon_environments); foreach ($pantheon_environments as $pantheon_environment) { - $result = $this->taskExec("terminus env:wake $project_machine_name.$pantheon_environment") + $result = $this->taskExec("terminus env:wake $project_name.$pantheon_environment") ->run() ->getExitCode(); if ($result !== 0) { $this->say("Failed to wake up the Pantheon $pantheon_environment environment."); continue; } - $result = $this->taskExec("terminus lock:enable $project_machine_name.$pantheon_environment $http_basic_auth_user $http_basic_auth_password") + $result = $this->taskExec("terminus lock:enable $project_name.$pantheon_environment $http_basic_auth_user $http_basic_auth_password") ->run() ->getExitCode(); if ($result !== 0) { @@ -369,10 +376,10 @@ public function lockPantheonEnvironments(string $project_machine_name, string $h * * @param string $project_name * The project name. - * @param string $organization + * @param string $github_organization * The GitHub organization. - * @param string $project_machine_name - * The project machine name in GH slug. + * @param string $github_repo_name + * The GitHub repository name. * @param string $terminus_token * The Pantheon machine token. * @param string $github_token @@ -382,7 +389,7 @@ public function lockPantheonEnvironments(string $project_machine_name, string $h * @param string $http_basic_auth_password * The HTTP basic auth password. */ - protected function verifyRequirements(string $project_name, string $organization, string $project_machine_name, string $terminus_token, string $github_token, $http_basic_auth_user, $http_basic_auth_password) { + protected function verifyRequirements(string $project_name, string $github_organization, string $github_repo_name, string $terminus_token, string $github_token, $http_basic_auth_user, $http_basic_auth_password) { if (is_dir('.bootstrap')) { throw new \Exception('The .bootstrap directory already exists. Please remove / move it and try again.'); } @@ -392,14 +399,13 @@ protected function verifyRequirements(string $project_name, string $organization if (empty(trim($project_name))) { throw new \Exception('The project name is empty.'); } - if (empty(trim($organization))) { - throw new \Exception('The organization is empty.'); - } - if (empty(trim($project_machine_name))) { - throw new \Exception('The project machine name is empty.'); + $this->validatePantheonSiteName($project_name); + + if (empty(trim($github_organization))) { + throw new \Exception('The GitHub organization is empty.'); } - if (str_contains($project_machine_name, ' ')) { - throw new \Exception('The project machine name contains spaces.'); + if (empty(trim($github_repo_name))) { + throw new \Exception('The GitHub repository name is empty.'); } if (empty(trim($terminus_token))) { throw new \Exception('The Pantheon machine token is empty.'); @@ -409,4 +415,22 @@ protected function verifyRequirements(string $project_name, string $organization } } + /** + * Validates a Pantheon site name. + * + * @param string $site_name + * The site name to validate. + * + * @throws \Exception + * If the site name is invalid. + */ + protected function validatePantheonSiteName(string $site_name): void { + if (strlen($site_name) >= 52) { + throw new \Exception("The site name '$site_name' must be fewer than 52 characters."); + } + if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$/', $site_name)) { + throw new \Exception("The site name '$site_name' can only contain a-z, A-Z, 0-9, and dashes, and cannot begin or end with a dash."); + } + } + }