Skip to content

Commit c4fe9fe

Browse files
Database backup pipeline.
1 parent 675a103 commit c4fe9fe

File tree

3 files changed

+283
-0
lines changed

3 files changed

+283
-0
lines changed

.github/workflows/database-backup.yml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: Database Backup
2+
3+
on:
4+
push:
5+
branches:
6+
- feature/DIGITAL-178-database-backup
7+
workflow_call:
8+
secrets:
9+
CF_USER:
10+
required: true
11+
CF_PASSWORD:
12+
required: true
13+
CF_ORG:
14+
required: true
15+
PROJECT:
16+
required: true
17+
DATABASE_BACKUP_BASTION_NAME:
18+
required: true
19+
schedule:
20+
- cron: "0 0 * * *"
21+
22+
jobs:
23+
backup-database:
24+
runs-on: ubuntu-latest
25+
continue-on-error: true
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v4
29+
- name: Set env
30+
run: source ./scripts/pipeline/github-exports.sh
31+
- name: Install basic dependancies
32+
run: ./scripts/pipeline/deb-basic-deps.sh
33+
- name: Install AWSCLI
34+
run: ./scripts/pipeline/awscli-install.sh
35+
- name: Install MySQL Client
36+
run: ./scripts/pipeline/deb-mysql-client-install.sh
37+
- name: Install Cloudfoundry CLI
38+
run: ./scripts/pipeline/deb-cf-install.sh
39+
- name: Cloud.gov login
40+
env:
41+
CF_USER: "${{ secrets.CF_USER }}"
42+
CF_PASSWORD: "${{ secrets.CF_PASSWORD }}"
43+
CF_ORG: "${{ secrets.CF_ORG }}"
44+
PROJECT: "${{ secrets.PROJECT }}"
45+
run: |
46+
source ./scripts/pipeline/cloud-gov-login.sh
47+
- name: Start Bastion
48+
env:
49+
DATABASE_BACKUP_BASTION_NAME: "${{ secrets.DATABASE_BACKUP_BASTION_NAME }}"
50+
PROJECT: "${{ secrets.PROJECT }}"
51+
run: |
52+
cf start "${PROJECT}-${DATABASE_BACKUP_BASTION_NAME}-${CF_SPACE}" >/dev/null 2>&1
53+
./scripts/pipeline/cloud-gov-wait-for-app-start.sh "${PROJECT}-${DATABASE_BACKUP_BASTION_NAME}-${CF_SPACE}"
54+
- name: Backup Database (dev)
55+
id: backup
56+
shell: bash
57+
env:
58+
CF_USER: "${{ secrets.CF_USER }}"
59+
CF_PASSWORD: "${{ secrets.CF_PASSWORD }}"
60+
CF_ORG: "${{ secrets.CF_ORG }}"
61+
PROJECT: "${{ secrets.PROJECT }}"
62+
DATABASE_BACKUP_BASTION_NAME: "${{ secrets.DATABASE_BACKUP_BASTION_NAME }}"
63+
run: |
64+
export TIMESTAMP=$(date --utc +%FT%TZ | tr ':', '-')
65+
source ./scripts/pipeline/database-backup.sh
66+
source ./scripts/pipeline/s3-backup-upload.sh
67+
stopBastion:
68+
name: Stop Bastion
69+
runs-on: ubuntu-latest
70+
needs: backup-database
71+
if: ${{ always() }}
72+
steps:
73+
- name: Checkout
74+
uses: actions/checkout@v4
75+
- name: Set env
76+
run: source ./scripts/pipeline/github-exports.sh
77+
- name: Install basic dependancies
78+
run: ./scripts/pipeline/deb-basic-deps.sh
79+
- name: Install Cloudfoundry CLI
80+
run: ./scripts/pipeline/deb-cf-install.sh
81+
- name: Cloud.gov login
82+
env:
83+
CF_USER: "${{ secrets.CF_USER }}"
84+
CF_PASSWORD: "${{ secrets.CF_PASSWORD }}"
85+
CF_ORG: "${{ secrets.CF_ORG }}"
86+
PROJECT: "${{ secrets.PROJECT }}"
87+
run: |
88+
source ./scripts/pipeline/cloud-gov-login.sh
89+
- name: Stop Bastion
90+
env:
91+
PROJECT: "${{ secrets.PROJECT }}"
92+
DATABASE_BACKUP_BASTION_NAME: "${{ secrets.DATABASE_BACKUP_BASTION_NAME }}"
93+
run: |
94+
cf stop "${PROJECT}-${DATABASE_BACKUP_BASTION_NAME}-${CF_SPACE}" >/dev/null 2>&1

scripts/pipeline/database-backup.sh

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/bin/bash
2+
3+
kill_pids() {
4+
app=$1
5+
ids=$(ps aux | grep "${app}" | grep -v grep | awk '{print $2}')
6+
for id in ${ids}; do
7+
kill -9 "${id}" >/dev/null 2>&1
8+
done
9+
}
10+
11+
## Wait for the tunnel to finish connecting.
12+
wait_for_tunnel() {
13+
while : ; do
14+
[ -n "$(grep 'Press Control-C to stop.' backup.txt)" ] && break
15+
echo "Waiting for tunnel..."
16+
sleep 1
17+
done
18+
}
19+
20+
date
21+
22+
## Create a tunnel through the application to pull the database.
23+
echo "Creating tunnel to database..."
24+
cf enable-ssh "${PROJECT}-drupal-${CF_SPACE}"
25+
cf restart --strategy rolling "${PROJECT}-drupal-${CF_SPACE}"
26+
cf connect-to-service --no-client "${PROJECT}-drupal-${CF_SPACE}" "${PROJECT}-mysql-${CF_SPACE}" > backup.txt &
27+
28+
wait_for_tunnel
29+
30+
date
31+
32+
## Create variables and credential file for MySQL login.
33+
echo "Backing up 'prod' database..."
34+
{
35+
host=$(cat backup.txt | grep -i host | awk '{print $2}')
36+
port=$(cat backup.txt | grep -i port | awk '{print $2}')
37+
username=$(cat backup.txt | grep -i username | awk '{print $2}')
38+
password=$(cat backup.txt | grep -i password | awk '{print $2}')
39+
dbname=$(cat backup.txt | grep -i '^name' | awk '{print $2}')
40+
41+
mkdir ~/.mysql && chmod 0700 ~/.mysql
42+
43+
echo "[mysqldump]" > ~/.mysql/mysqldump.cnf
44+
echo "user=${username}" >> ~/.mysql/mysqldump.cnf
45+
echo "password=${password}" >> ~/.mysql/mysqldump.cnf
46+
chmod 400 ~/.mysql/mysqldump.cnf
47+
48+
## Exclude tables without data
49+
declare -a excluded_tables=(
50+
"cache_advagg_minify"
51+
"cache_bootstrap"
52+
"cache_config"
53+
"cache_container"
54+
"cache_data"
55+
"cache_default"
56+
"cache_discovery"
57+
"cache_discovery_migration"
58+
"cache_dynamic_page_cache"
59+
"cache_entity"
60+
"cache_menu"
61+
"cache_migrate"
62+
"cache_page"
63+
"cache_render"
64+
"cache_rest"
65+
"cache_toolbar"
66+
"sessions"
67+
"watchdog"
68+
"webprofiler"
69+
)
70+
71+
ignored_tables_string=''
72+
for table in "${excluded_tables[@]}"
73+
do
74+
ignored_tables_string+=" --ignore-table=${dbname}.${table}"
75+
done
76+
77+
## Dump structure
78+
mysqldump \
79+
--defaults-extra-file=~/.mysql/mysqldump.cnf \
80+
--host="${host}" \
81+
--port="${port}" \
82+
--protocol=TCP \
83+
--no-data \
84+
"${dbname}" > "backup_${CF_SPACE}.sql"
85+
86+
## Dump content
87+
mysqldump \
88+
--defaults-extra-file=~/.mysql/mysqldump.cnf \
89+
--host="${host}" \
90+
--port="${port}" \
91+
--protocol=TCP \
92+
--no-create-info \
93+
--skip-triggers \
94+
"${ignored_tables_string}" \
95+
"${dbname}" >> "backup_${CF_SPACE}.sql"
96+
97+
## Patch out any MySQL 'SET' commands that require admin.
98+
sed -i 's/^SET /-- &/' "backup_${CF_SPACE}.sql"
99+
100+
} >/dev/null 2>&1
101+
102+
date
103+
104+
## Kill the backgrounded SSH tunnel.
105+
echo "Cleaning up old connections..."
106+
{
107+
kill_pids "connect-to-service"
108+
} >/dev/null 2>&1
109+
110+
## Disable ssh.
111+
echo "Disabling ssh..."
112+
cf disable-ssh "${PROJECT}-drupal-${CF_SPACE}"
113+
114+
rm -rf backup.txt ~/.mysql
115+
116+
echo "Saving to backup bucket..."
117+
{
118+
cf target -s "${PROJECT}-${CF_SPACE}" >/dev/null 2>&1
119+
120+
export service="${PROJECT}-backup"
121+
export service_key="${service}-key"
122+
cf delete-service-key "${service}" "${service_key}" -f >/dev/null 2>&1
123+
cf create-service-key "${service}" "${service_key}" >/dev/null 2>&1
124+
sleep 2
125+
126+
s3_credentials=$(cf service-key "${service}" "${service_key}" | tail -n +2)
127+
export s3_credentials
128+
129+
AWS_ACCESS_KEY_ID=$(echo "${s3_credentials}" | jq -r '.credentials.access_key_id')
130+
export AWS_ACCESS_KEY_ID
131+
132+
bucket=$(echo "${s3_credentials}" | jq -r '.credentials.bucket')
133+
export bucket
134+
135+
AWS_DEFAULT_REGION=$(echo "${s3_credentials}" | jq -r '.credentials.region')
136+
export AWS_DEFAULT_REGION
137+
138+
AWS_SECRET_ACCESS_KEY=$(echo "${s3_credentials}" | jq -r '.credentials.secret_access_key')
139+
export AWS_SECRET_ACCESS_KEY
140+
141+
# copy latest database to top level
142+
gzip "backup_${CF_SPACE}.sql"
143+
aws s3 cp "./backup_${CF_SPACE}.sql.gz" "s3://${bucket}/${CF_SPACE}/latest.sql.gz" --no-verify-ssl >/dev/null 2>&1 && echo "Successfully copied latest.sql.gz to S3!" || echo "Failed to copy latest.sql.gz to S3!"
144+
145+
cf delete-service-key "${service}" "${service_key}" -f >/dev/null 2>&1
146+
}
147+
148+
date

scripts/pipeline/github-exports.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
3+
#BRANCH=$(echo $GITHUB_REF | cut -d'/' -f 3)
4+
BRANCH=develop
5+
COMPOSER_DEV=1
6+
GSA_AUTH_KEY=${{ secrets.GSA_AUTH_DEVELOPMENT_KEY }}
7+
case ${BRANCH} in
8+
develop)
9+
CF_SPACE="dev"
10+
DRUPAL_MEMORY=${{ vars.DEVELOP_CMS_MEMORY }}
11+
DRUPAL_INSTANCES=${{ vars.DEVELOP_INSTANCES }}
12+
;;
13+
main)
14+
CF_SPACE="prod"
15+
COMPOSER_DEV=0
16+
DRUPAL_MEMORY=${{ vars.MAIN_CMS_MEMORY }}
17+
DRUPAL_INSTANCES=${{ vars.MAIN_INSTANCES }}
18+
GSA_AUTH_KEY=${{ secrets.GSA_AUTH_PRODUCTION_KEY }}
19+
;;
20+
stage)
21+
CF_SPACE="staging"
22+
COMPOSER_DEV=0
23+
DRUPAL_MEMORY=${{ vars.STAGE_CMS_MEMORY }}
24+
DRUPAL_INSTANCES=${{ vars.STAGE_INSTANCES }}
25+
;;
26+
esac
27+
28+
echo "APP_NAME=drupal" | tee -a $GITHUB_ENV
29+
echo "BRANCH=${BRANCH}" | tee -a $GITHUB_ENV
30+
echo "BUILDPACK_PORT=${{ vars.BUILDPACK_PORT }}" | tee -a $GITHUB_ENV
31+
echo "CF_SPACE=${CF_SPACE}" | tee -a $GITHUB_ENV
32+
echo "COMPOSER_DEV=${COMPOSER_DEV}" | tee -a $GITHUB_ENV
33+
echo "DRUPAL_INSTANCES=${DRUPAL_INSTANCES}" | tee -a $GITHUB_ENV
34+
echo "DRUPAL_MEMORY=${DRUPAL_MEMORY}" | tee -a $GITHUB_ENV
35+
echo "GSA_AUTH_KEY=${GSA_AUTH_KEY}" | tee -a $GITHUB_ENV
36+
echo "HASH_SALT=${{ secrets.HASH_SALT }}" | tee -a $GITHUB_ENV
37+
echo "WAF_NAME=waf"| tee -a $GITHUB_ENV
38+
39+
if [ "${COMPOSER_DEV}" = "1" ]; then
40+
sed -i 's/--no-dev //' .bp-config/options.json || exit 0
41+
fi

0 commit comments

Comments
 (0)