diff --git a/.github/actions/drainpipe/acquia/clone-env/action.yml b/.github/actions/drainpipe/acquia/clone-env/action.yml new file mode 100644 index 000000000..2c0072f89 --- /dev/null +++ b/.github/actions/drainpipe/acquia/clone-env/action.yml @@ -0,0 +1,24 @@ +name: "Clone Environment" +description: "Clones an Acquia Environment to another site" +inputs: + source-environment: + description: "Source environment i.e. the uuid or environment alias" + required: true + target-environment: + description: "Target environment i.e. the uuid or environment alias" + required: true + api-key: + description: "Acquia API Key" + required: true + api-secret: + description: "Acquia API Secret" + required: true +runs: + using: "composite" + steps: + - name: Clone Environment + run: | + source .github/actions/drainpipe/common/set-env/bash_aliases + drainpipe_exec "ACQUIA_API_KEY=${{ inputs.api-key }} ACQUIA_API_SECRET=${{ inputs.api-secret }} ./vendor/bin/task acquia:auth" + drainpipe_exec "acli env:mirror --no-config --no-interaction ${{ inputs.source-environment }} ${{ inputs.target-environment }}" + shell: bash diff --git a/.github/actions/drainpipe/acquia/deploy/action.yml b/.github/actions/drainpipe/acquia/deploy/action.yml new file mode 100644 index 000000000..0a98b91b9 --- /dev/null +++ b/.github/actions/drainpipe/acquia/deploy/action.yml @@ -0,0 +1,67 @@ +name: 'Deploy a branch to Acquia' +description: 'Deploys a review app to an Acquia Environment' +inputs: + github-token: + description: "GitHub token as generated automatically in secrets.GITHUB_TOKEN" + required: true + environment: + description: "The environment to push to, either uuid or alias" + required: true + environment-url: + description: "The environment URL" + required: true + run-installer: + description: "Whether or not to run the Drupal site installer. Defaults to false." + required: false + commit-message: + description: "The commit message to use when pushing to Acquia" + required: true + api-key: + description: "Acquia API Key" + required: true + api-secret: + description: "Acquia API Secret" + required: true +runs: + using: "composite" + steps: + - name: Create GitHub Deployment + uses: .github/actions/drainpipe/common/deployment-create + with: + github-token: ${{ inputs.github-token }} + environment: ${{ inputs.environment }} + + - name: Put site in Maintenance Mode + run: | + source .github/actions/drainpipe/common/set-env/bash_aliases + drainpipe_exec "ACQUIA_API_KEY=${{ inputs.api-key }} ACQUIA_API_SECRET=${{ inputs.api-secret }} ./vendor/bin/task acquia:auth" + ENVIRONMENT="${{ inputs.environment }}" + APPLICATION=${ENVIRONMENT%.*} + drainpipe_exec "acli remote:aliases:download --no-interaction $APPLICATION" + drainpipe_exec "./vendor/bin/task drupal:maintenance:on site=@lullabot8.dev" + shell: bash + + - name: Push to Acquia + uses: .github/actions/drainpipe/acquia/push + with: + environment: ${{ inputs.environment }} + commit-message: ${{ inputs.commit-message }} + api-key: ${{ inputs.api-key }} + api-secret: ${{ inputs.api-secret }} + + - name: Run updates + uses: .github/actions/drainpipe/acquia/update + with: + environment: ${{ inputs.environment }} + run-installer: ${{ inputs.run-installer }} + + - name: Set Deployment Status + uses: .github/actions/drainpipe/common/deployment-status + with: + github-token: ${{ inputs.github-token }} + environment-url: ${{ inputs.environment-url }} + + - name: Take site out of Maintenance Mode + run: | + drainpipe_exec "./vendor/bin/task drupal:maintenance:on site=@${{ inputs.environment }}" + shell: bash diff --git a/.github/actions/drainpipe/acquia/push/action.yml b/.github/actions/drainpipe/acquia/push/action.yml new file mode 100644 index 000000000..f165401e4 --- /dev/null +++ b/.github/actions/drainpipe/acquia/push/action.yml @@ -0,0 +1,35 @@ +name: 'Push code to Acquia' +description: 'Pushes code to an Acquia environment' +inputs: + environment: + description: "The environment to push to, either uuid or alias" + required: true + commit-message: + description: "The commit message to use when pushing to Acquia" + required: true + api-key: + description: "Acquia API Key" + required: true + api-secret: + description: "Acquia API Secret" + required: true +runs: + using: "composite" + steps: + - name: Push to Acquia + run: | + source .github/actions/drainpipe/common/set-env/bash_aliases + drainpipe_exec "ACQUIA_API_KEY=${{ inputs.api-key }} ACQUIA_API_SECRET=${{ inputs.api-secret }} ./vendor/bin/task acquia:auth" + ENV_INFO=$(drainpipe_exec "acli api:environments:find ${{ inputs.environment }}") + VCS_TYPE=$(echo $ENV_INFO | jq -r ".vcs.type") + if [ "$VCS_TYPE" != "git" ]; then + echo "Unrecognised VCS type" + exit 1 + fi + BRANCH=$(echo $ENV_INFO | jq -r ".vcs.path") + REMOTE=$(echo $ENV_INFO | jq -r ".vcs.url") + drainpipe_exec "./vendor/bin/task deploy:git directory=/tmp/release branch=\"$BRANCH\" remote=\"$REMOTE\" message=\"${{ inputs.commit-message }}\"" + # Run code-switch to the same branch so we wait for everything to sync. + drainpipe_exec "acli api:environments:code-switch ${{ inputs.environment }} \"$BRANCH\"" + shell: bash + diff --git a/.github/actions/drainpipe/acquia/update/action.yml b/.github/actions/drainpipe/acquia/update/action.yml new file mode 100644 index 000000000..512ba70ad --- /dev/null +++ b/.github/actions/drainpipe/acquia/update/action.yml @@ -0,0 +1,27 @@ +name: "Update or Install Acquia Site" +description: "Runs the Drupal updater or the site installer in an Acquia environment" +inputs: + environment: + description: "The environment to run the updates or install in, either uuid or alias e.g. 'test'" + required: true + run-installer: + description: "Whether or not to run the Drupal site installer. Defaults to false." + required: false +runs: + using: "composite" + steps: + - name: Update site on Acquia + run: | + source .github/actions/drainpipe/common/set-env/bash_aliases + drainpipe_exec "ACQUIA_API_KEY=${{ inputs.api-key }} ACQUIA_API_SECRET=${{ inputs.api-secret }} ./vendor/bin/task acquia:auth" + ENVIRONMENT="${{ inputs.environment }}" + APPLICATION=${ENVIRONMENT%.*} + drainpipe_exec "acli remote:aliases:download --no-interaction $APPLICATION" + if [ "${{ inputs.run-installer }}" == "true" ]; then + drainpipe_exec "./vendor/bin/drush @${{ inputs.environment }} --yes site:install --existing-config" + elif drainpipe_exec "./vendor/bin/task -l | grep '* update: ')"; then + drainpipe_exec "./vendor/bin/task update site=@${{ inputs.environment }}" + else + drainpipe_exec "./vendor/bin/task drupal:update site=@${{ inputs.environment }}" + fi + shell: bash diff --git a/.github/actions/drainpipe/common/deployment-create/action.yml b/.github/actions/drainpipe/common/deployment-create/action.yml new file mode 100644 index 000000000..61d857697 --- /dev/null +++ b/.github/actions/drainpipe/common/deployment-create/action.yml @@ -0,0 +1,28 @@ +name: 'Create a GitHub deployment' +description: 'Creates a GitHub deployment and echos the ID to the $GITHUB_ENV file' +inputs: + github-token: + description: "GitHub token as generated automatically in secrets.GITHUB_TOKEN" + required: true + environment: + description: "The environment name" + required: true +runs: + using: "composite" + steps: + - uses: ./.github/actions/drainpipe/set-env + + - name: Create GitHub Deployment + run: | + export GITHUB_DEPLOYMENT=$(curl -f -X POST \ + https://api.github.com/repos/$GITHUB_REPOSITORY/deployments \ + -H 'Accept: application/vnd.github.v3+json' \ + -H "Authorization: token ${{ inputs.github-token }}" \ + -d "{\"ref\": \"$DRAINPIPE_SHA\", \"auto_merge\": false, \"environment\": \"${{ inputs.environment }}\", \"transient_environment\": false, \"required_contexts\": [], \"description\": \"Acquia Cloud environment\"}" \ + ) + export GITHUB_DEPLOYMENT_ID=$(echo $GITHUB_DEPLOYMENT | jq '.id') + echo "GITHUB_DEPLOYMENT_ID=$GITHUB_DEPLOYMENT_ID" >> $GITHUB_ENV + echo "Created GitHub Deployment ID $GITHUB_DEPLOYMENT_ID" + if [ -z "$GITHUB_DEPLOYMENT_ID" ] || [ "$GITHUB_DEPLOYMENT_ID" = "null" ]; then echo $GITHUB_DEPLOYMENT && exit 1; fi + curl -f -H "Authorization: token ${{ inputs.github-token }}" -X POST -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/$GITHUB_REPOSITORY/deployments/$GITHUB_DEPLOYMENT_ID/statuses -d '{"state":"in_progress"}' + shell: bash diff --git a/.github/actions/drainpipe/common/deployment-status/action.yml b/.github/actions/drainpipe/common/deployment-status/action.yml new file mode 100644 index 000000000..db14e5e81 --- /dev/null +++ b/.github/actions/drainpipe/common/deployment-status/action.yml @@ -0,0 +1,24 @@ +name: 'Sets the GitHub Deployment status' +description: 'Sets a GitHub deployment to success or failure' +inputs: + github-token: + description: "GitHub token as generated automatically in secrets.GITHUB_TOKEN" + required: true + environment-url: + description: "The environment URL" + required: true +runs: + using: "composite" + steps: + - uses: ./.github/actions/drainpipe/set-env + + - name: Set deployment to success + run: | + curl -f -H "Authorization: token ${{ inputs.github-token }}" -X POST -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/$GITHUB_REPOSITORY/deployments/$GITHUB_DEPLOYMENT_ID/statuses -d "{\"state\":\"success\", \"environment_url\": \"${{ inputs.environment-url }}\"}" + shell: bash + + - name: Set Deployment Failure Status + run: | + curl -f -H "Authorization: token ${{ inputs.github-token }}" -X POST -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/$GITHUB_REPOSITORY/deployments/$GITHUB_DEPLOYMENT_ID/statuses -d "{\"state\":\"failure\"}" + if: ${{ failure() }} + shell: bash diff --git a/.github/actions/drainpipe/common/set-env/action.yml b/.github/actions/drainpipe/common/set-env/action.yml new file mode 100644 index 000000000..32862b6fe --- /dev/null +++ b/.github/actions/drainpipe/common/set-env/action.yml @@ -0,0 +1,17 @@ +name: 'Set Env' +description: 'Creates some useful environment variables' +inputs: + github-api-token: + description: "GitHub API token" + required: true + github-api-token-username: + description: "GitHub API token username" + required: true +runs: + using: "composite" + steps: + - run: | + echo "Setting Drainpipe environment variables:" + echo "DRAINPIPE_PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')" >> $GITHUB_ENV + echo "DRAINPIPE_SHA=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.head.sha)" >> $GITHUB_ENV + shell: bash diff --git a/.github/actions/drainpipe/common/set-env/bash_aliases b/.github/actions/drainpipe/common/set-env/bash_aliases new file mode 100644 index 000000000..58fca27a8 --- /dev/null +++ b/.github/actions/drainpipe/common/set-env/bash_aliases @@ -0,0 +1,7 @@ +drainpipe_exec() { + if [ "$DRAINPIPE_DDEV" == "true" ]; then + ddev exec "$@" + else + eval "$@" + fi +} diff --git a/.github/workflows/TestAcquia.yml b/.github/workflows/TestAcquia.yml new file mode 100644 index 000000000..27b08f4bb --- /dev/null +++ b/.github/workflows/TestAcquia.yml @@ -0,0 +1,90 @@ +name: "Test Acquia" + +on: + pull_request: + types: [opened, synchronize, reopened] + +concurrency: development + +jobs: + Test-Acquia: + runs-on: ubuntu-22.04 + steps: + - name: Create a Drupal project + run: composer create-project drupal/recommended-project . --ignore-platform-req=ext-gd + + - uses: actions/checkout@v4 + with: + path: drainpipe + + - uses: ./drainpipe/scaffold/github/actions/common/set-env + + - name: Install DDEV + uses: ./drainpipe/scaffold/github/actions/common/ddev + with: + git-name: Drainpipe Bot + git-email: no-reply@example.com + ssh-private-key: ${{ secrets.ACQUIA_SSH_PRIVATE_KEY }} + + - name: Setup Project + run: | + ddev config --auto + ddev start + ddev composer config extra.drupal-scaffold.gitignore true + ddev composer config --json extra.drupal-scaffold.allowed-packages \[\"lullabot/drainpipe\"] + ddev composer config --no-plugins allow-plugins.composer/installers true + ddev composer config --no-plugins allow-plugins.drupal/core-composer-scaffold true + ddev composer config --no-plugins allow-plugins.lullabot/drainpipe true + ddev composer config repositories.drainpipe --json '{"type": "path", "url": "drainpipe", "options": {"symlink": false}}' + ddev composer config extra.drainpipe.acquia --json '{"settings": true, "github": []}' + ddev composer config minimum-stability dev + ddev composer require lullabot/drainpipe --with-all-dependencies + + - name: Install Drupal + run: | + ddev drush site:install minimal -y + echo "\$settings['config_sync_directory'] = '../config';" >> web/sites/default/settings.php + ddev drush config:export -y + + - name: Create .drainpipeignore + run: | + echo "/web/sites/default/files" >> .drainpipeignore + echo "/.ddev" >> .drainpipeignore + echo "settings.ddev.php" >> .drainpipeignore + echo "/drainpipe" >> .drainpipeignore + + - name: Create settings.php + run: | + echo ' web/sites/default/settings.php + echo "\$settings['container_yamls'][] = __DIR__ . '/services.yml';" >> web/sites/default/settings.php + echo "include __DIR__ . \"/settings.acquia.php\";" >> web/sites/default/settings.php + echo "\$settings['config_sync_directory'] = '../config';" >> web/sites/default/settings.php + + - name: Snapshot Project + env: + directory: /tmp/release + remote: + message: + site: + run: | + echo "/drainpipe" >> .drainpipeignore + ddev task snapshot:directory directory=/tmp/release + + - name: Clone from production to dev + uses: ./drainpipe/scaffold/github/actions/acquia/clone-env + with: + source-environment: lullabotsandbox.prod + target-environment: lullabotsandbox.dev + api-key: ${{ secrets.ACQUIA_API_KEY }} + api-secret: ${{ secrets.ACQUIA_API_SECRET }} + + - name: Deploy to dev + uses: ./drainpipe/scaffold/github/actions/acquia/deploy + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + environment: lullabotsandbox.dev + environment-url: https://lullabotsandboxffi2ugpgwh.devcloud.acquia-sites.com + run-installer: true + commit-message: ${{ github.sha }} + api-key: ${{ secrets.ACQUIA_API_KEY }} + api-secret: ${{ secrets.ACQUIA_API_SECRET }} diff --git a/README.md b/README.md index c623240b4..bb4625092 100644 --- a/README.md +++ b/README.md @@ -362,6 +362,28 @@ ddev ssh terminus site:upstream:set [site_name] empty ``` +### Acquia +Acquia specific tasks are contained in [`tasks/acquia.yml`](tasks/acquia.yml). +Add the following to your `Taskfile.yml`'s `includes` section to use them: +```yml +includes: + acquia: ./vendor/lullabot/drainpipe/tasks/acquia.yml +``` +| | | +|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `task acquia:fetch-db` | Fetches a database from Acquia. Set `ACQUIA_ENVIRONMENT_ID` in Taskfile `vars`, along with `ACQUIA_API_KEY` and `ACQUIA_API_SECRET` as environment variables | + +To enable auto configuration of Acquia Cloud settings: +```json +"extra": { + "drainpipe": { + "acquia": { + "settings": true + }, + } +} +``` + ## GitHub Actions Integration @@ -460,6 +482,16 @@ To enable deployment of Pantheon Review Apps: - `PANTHEON_REVIEW_USERNAME` (optional) A username for HTTP basic auth local - `PANTHEON_REVIEW_PASSWORD` (optional) The password to lock the site with +### Acquia +To add Acquia specific GitHub actions, add the following composer.json + ```json + "extra": { + "drainpipe": { + "github": ["Acquia"] + } + } + ``` + ## GitLab CI Integration Add the following to `composer.json` for GitLab helpers: diff --git a/composer.json b/composer.json index 2bf7de982..d04c53a4d 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,6 @@ } }, "archive": { - "exclude": ["/.github", "/.yarn", "/drainpipe-dev", "/metapackages"] + "exclude": ["/.github/workflows", "/.yarn", "/drainpipe-dev", "/metapackages"] } } diff --git a/scaffold/acquia/.drainpipeignore b/scaffold/acquia/.drainpipeignore new file mode 100644 index 000000000..70250dbfb --- /dev/null +++ b/scaffold/acquia/.drainpipeignore @@ -0,0 +1,3 @@ +# Acquia reserved paths +/docroot/sites/default/files +/sites/default/files diff --git a/scaffold/acquia/settings.acquia.php b/scaffold/acquia/settings.acquia.php new file mode 100644 index 000000000..6c19e2100 --- /dev/null +++ b/scaffold/acquia/settings.acquia.php @@ -0,0 +1,70 @@ +installTaskfile(); $this->installGitignore(); $this->installDdevCommand(); + $this->installHostingProviderSupport(); $this->installCICommands(); $this->installEnvSupport(); } @@ -96,6 +97,7 @@ public function onPostUpdateCmd(Event $event) $this->installTaskfile(); $this->installGitignore(); $this->installDdevCommand(); + $this->installHostingProviderSupport(); $this->installCICommands(); $this->installEnvSupport(); } @@ -226,6 +228,35 @@ private function installDdevCommand(): void } } + /** + * Install hosting provider support. + */ + private function installHostingProviderSupport(): void + { + $fs = new Filesystem(); + $scaffoldPath = $this->config->get('vendor-dir') . '/lullabot/drainpipe/scaffold'; + if (isset($this->extra['drainpipe']['acquia'])) { + if (!file_exists('.drainpipeignore')) { + $fs->copy("$scaffoldPath/acquia/.drainpipeignore", '.drainpipeignore'); + } + if (!empty($this->extra['drainpipe']['acquia']['settings'])) { + // settings.acquia.php + if (!file_exists('./web/sites/default/settings.acquia.php')) { + $fs->copy("$scaffoldPath/acquia/settings.acquia.php", './web/sites/default/settings.acquia.php'); + } + if (file_exists('./web/sites/default/settings.php')) { + $settings = file_get_contents('./web/sites/default/settings.php'); + if (strpos($settings, 'settings.acquia.php') === false) { + $include = <<<'EOT' +include __DIR__ . "/settings.acquia.php"; +EOT; + file_put_contents('./web/sites/default/settings.php', $include . PHP_EOL, FILE_APPEND); + } + } + } + } + } + /** * Install CI Commands. */ @@ -333,6 +364,11 @@ private function installGitHubActions(string $scaffoldPath): void { $fs->copy("$scaffoldPath/github/workflows/PantheonReviewApps.yml", './.github/workflows/PantheonReviewApps.yml'); } } + else if ($github === 'acquia') { + $fs->ensureDirectoryExists('./.github/actions/drainpipe/acquia'); + $fs->ensureDirectoryExists('./.github/workflows'); + $fs->copy("$scaffoldPath/github/actions/acquia", './.github/actions/drainpipe/acquia'); + } else if ($github === 'ComposerLockDiff') { $fs->ensureDirectoryExists('./.github/workflows'); $fs->copy("$scaffoldPath/github/workflows/ComposerLockDiff.yml", './.github/workflows/ComposerLockDiff.yml'); @@ -350,6 +386,9 @@ private function installGitHubActions(string $scaffoldPath): void { $fs->copy("$scaffoldPath/github/workflows/TestFunctional.yml", './.github/workflows/TestFunctional.yml'); } } + + if (isset($this->extra['drainpipe']['acquia'])) { + } } /** diff --git a/tasks/acquia.yml b/tasks/acquia.yml new file mode 100644 index 000000000..29a538d84 --- /dev/null +++ b/tasks/acquia.yml @@ -0,0 +1,23 @@ +version: '3' + +tasks: + auth: + cmds: + - if [ -z "$ACQUIA_API_KEY" ]; then echo "ACQUIA_API_KEY is empty, please add it to your .env file"; exit 1; fi + - if [ -z "$ACQUIA_API_SECRET" ]; then echo "ACQUIA_API_SECRET is empty, please add it to your .env file"; exit 1; fi + - echo "💧 Authorising with Acquia" + - acli auth:login --no-interaction --key=${ACQUIA_API_KEY} --secret=${ACQUIA_API_SECRET} + fetch-db: + desc: "Fetches a database from Acquia" + env: + DB_DIR: '{{default "/var/www/html/files/db" .DB_DIR}}' + DB_NAME: '{{default "default" .DB_NAME}}' + cmds: + - mkdir -p $DB_DIR + - rm -f $DB_DIR/db.sql.gz $DB_DIR/db.sql + - task: auth + - echo "💧 Fetching database from Acquia" + - acli pull:database {{.ACQUIA_ENVIRONMENT_ID}} $DB_NAME --no-import --no-interaction + - | + DB=$(ls -t /tmp/*.sql.gz | head -1) + mv $DB $DB_DIR/db.sql.gz diff --git a/tests/local-test.sh b/tests/local-test.sh index 7604fa698..22846f091 100755 --- a/tests/local-test.sh +++ b/tests/local-test.sh @@ -17,6 +17,7 @@ ddev composer config --no-plugins allow-plugins.lullabot/drainpipe true ddev composer config --no-plugins allow-plugins.lullabot/drainpipe-dev true ddev composer config repositories.drainpipe --json '{"type": "path", "url": "drainpipe", "options": {"symlink": false}}' ddev composer config repositories.drainpipe-dev --json '{"type": "path", "url": "drainpipe/drainpipe-dev", "options": {"symlink": false}}' +ddev composer config extra.drainpipe.acquia --json '{"settings": true, "github": []}' ddev composer config minimum-stability dev ddev composer require "lullabot/drainpipe @dev" --with-all-dependencies ddev composer require "lullabot/drainpipe-dev @dev" --dev --with-all-dependencies