Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
44 changes: 23 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,35 +383,37 @@ 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
1. Run the autodeploy configuration command:
```bash
ddev robo deploy:config-autodeploy [your terminus token] [your github token]
```

**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. Actualize `public static string $githubProject = 'Gizra/the-client';` in the `RoboFile.php`.
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)
- 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`.

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]`
### 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

Expand Down
22 changes: 17 additions & 5 deletions robo-components/BootstrapTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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/$github_organization/$project_machine_name#automatic-deployment-to-pantheon");
}

/**
Expand Down
176 changes: 124 additions & 52 deletions robo-components/DeploymentTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace RoboComponents;

use Robo\Symfony\ConsoleIO;
use Symfony\Component\Yaml\Yaml;

/**
Expand Down Expand Up @@ -693,24 +694,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'];

Expand All @@ -720,63 +718,137 @@ 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();
$this->say("The project was prepared for automatic deployment to Pantheon using GitHub Actions");
$this->say("");

// Check if gh CLI is available.
$gh_available = $this->taskExec('which gh')
->printOutput(FALSE)
->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 = [
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (gh check and init) should be in a helper function, to keep deployConfigAutodeploy more readable. refactor accordingly

'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;
}
}

$result = $this->taskExec('git add pantheon-key.enc')->run();
if ($result->getExitCode() !== 0) {
throw new \Exception("git add failed.");
if ($gh_available) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This is not enough. We are inside DDEV, we can sudo and install gh if it's missing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 9dca072. The command now automatically installs gh CLI using sudo if it's not found in the DDEV environment. Installation uses apt with quiet flags to minimize output noise. If installation fails, it falls back to manual instructions.

$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);

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("");
$this->printManualInstructions($token, $github_token, $pantheon_git_url);
}

$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("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("3. Review and commit the encrypted key file (pantheon-key.enc)");
$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("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");
}

/**
* 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");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Can we automate it with gh CLI? If yes, we can install it on-demand inside DDEV and help the user to accomplish this!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Added gh CLI automation in commit 82479da.

The command now:

  • Detects if gh CLI is available
  • Offers to automatically set up GitHub Secrets (TERMINUS_TOKEN, PANTHEON_DEPLOY_KEY, GH_TOKEN) and Variables (PANTHEON_GIT_URL)
  • Falls back to manual instructions if gh CLI is not available or user declines

The gh CLI can be installed on-demand in DDEV, and users will be prompted to confirm before any secrets are set.

$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("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)");
}

/**
Expand Down
Loading