diff --git a/.github/workflows/database-backup-dev.yml b/.github/workflows/database-backup-dev.yml new file mode 100644 index 00000000..048c6d14 --- /dev/null +++ b/.github/workflows/database-backup-dev.yml @@ -0,0 +1,132 @@ +name: Database Backup + +permissions: + contents: read + +on: + workflow_call: + secrets: + CF_USER: + required: true + CF_PASSWORD: + required: true + CF_ORG: + required: true + PROJECT: + required: true + DATABASE_BACKUP_BASTION_NAME: + required: true + +jobs: + startBastion: + name: Start Bastion + runs-on: ubuntu-latest + if: github.ref_protected == true + steps: + - name: Checkout + uses: actions/checkout@v6.0.2 + - name: Set env + run: | + BRANCH=develop + CF_SPACE="dev" + + echo "BRANCH=${BRANCH}" | tee -a $GITHUB_ENV + echo "CF_SPACE=${CF_SPACE}" | tee -a $GITHUB_ENV + + - name: Install basic dependencies + run: ./scripts/pipeline/deb-basic-deps.sh + - name: Install Cloudfoundry CLI + run: ./scripts/pipeline/deb-cf-install.sh + - name: Cloud.gov login + env: + CF_USER: "${{ secrets.CF_USER }}" + CF_PASSWORD: "${{ secrets.CF_PASSWORD }}" + CF_ORG: "${{ secrets.CF_ORG }}" + PROJECT: "${{ secrets.PROJECT }}" + run: | + source ./scripts/pipeline/cloud-gov-login.sh + - name: Start Bastion + env: + PROJECT: "${{ secrets.PROJECT }}" + DATABASE_BACKUP_BASTION_NAME: "${{ secrets.DATABASE_BACKUP_BASTION_NAME }}" + run: | + cf start "${PROJECT}-${DATABASE_BACKUP_BASTION_NAME}-${CF_SPACE}" >/dev/null 2>&1 + ./scripts/pipeline/cloud-gov-wait-for-app-start.sh "${PROJECT}-${DATABASE_BACKUP_BASTION_NAME}-${CF_SPACE}" + backup-database: + runs-on: ubuntu-latest + continue-on-error: true + needs: startBastion + if: github.ref_protected == true + steps: + - name: Checkout + uses: actions/checkout@v6.0.2 + - name: Set env + run: | + BRANCH=develop + CF_SPACE="dev" + + echo "BRANCH=${BRANCH}" | tee -a $GITHUB_ENV + echo "CF_SPACE=${CF_SPACE}" | tee -a $GITHUB_ENV + + - name: Install basic dependencies + run: ./scripts/pipeline/deb-basic-deps.sh + - name: Install AWSCLI + run: ./scripts/pipeline/deb-awscli.sh + - name: Install MySQL Client + run: ./scripts/pipeline/deb-mysql-client-install.sh + - name: Install Cloudfoundry CLI + run: ./scripts/pipeline/deb-cf-install.sh + - name: Cloud.gov login + env: + CF_USER: "${{ secrets.CF_USER }}" + CF_PASSWORD: "${{ secrets.CF_PASSWORD }}" + CF_ORG: "${{ secrets.CF_ORG }}" + PROJECT: "${{ secrets.PROJECT }}" + run: | + source ./scripts/pipeline/cloud-gov-login.sh + - name: Backup Database + id: backup + shell: bash + env: + CF_USER: "${{ secrets.CF_USER }}" + CF_PASSWORD: "${{ secrets.CF_PASSWORD }}" + CF_ORG: "${{ secrets.CF_ORG }}" + PROJECT: "${{ secrets.PROJECT }}" + DATABASE_BACKUP_BASTION_NAME: "${{ secrets.DATABASE_BACKUP_BASTION_NAME }}" + run: | + export TIMESTAMP=$(date --utc +%FT%TZ | tr ':', '-') + source ./scripts/pipeline/database-backup.sh + stopBastion: + name: Stop Bastion + runs-on: ubuntu-latest + needs: backup-database + if: github.ref_protected == true && ${{ always() }} + steps: + - name: Checkout + uses: actions/checkout@v6.0.2 + - name: Set env + run: | + BRANCH=develop + CF_SPACE="dev" + + echo "BRANCH=${BRANCH}" | tee -a $GITHUB_ENV + echo "CF_SPACE=${CF_SPACE}" | tee -a $GITHUB_ENV + + - name: Install basic dependencies + run: ./scripts/pipeline/deb-basic-deps.sh + - name: Install Cloudfoundry CLI + run: ./scripts/pipeline/deb-cf-install.sh + - name: Cloud.gov login + env: + CF_USER: "${{ secrets.CF_USER }}" + CF_PASSWORD: "${{ secrets.CF_PASSWORD }}" + CF_ORG: "${{ secrets.CF_ORG }}" + PROJECT: "${{ secrets.PROJECT }}" + run: | + source ./scripts/pipeline/cloud-gov-login.sh + - name: Stop Bastion + env: + PROJECT: "${{ secrets.PROJECT }}" + DATABASE_BACKUP_BASTION_NAME: "${{ secrets.DATABASE_BACKUP_BASTION_NAME }}" + run: | + cf stop "${PROJECT}-${DATABASE_BACKUP_BASTION_NAME}-${CF_SPACE}" >/dev/null 2>&1 diff --git a/.github/workflows/database-restore-dev.yml b/.github/workflows/database-restore-dev.yml new file mode 100644 index 00000000..0970e9d9 --- /dev/null +++ b/.github/workflows/database-restore-dev.yml @@ -0,0 +1,141 @@ +name: Database Restore + +permissions: + contents: read + +on: + workflow_dispatch: + inputs: + database_file_override: + description: "Path on S3 bucket to a specific database file to restore." + required: false +jobs: + start-bastion: + name: Start Bastion + runs-on: ubuntu-latest + if: ${{ always() }} + steps: + - name: Checkout + uses: actions/checkout@v6.0.2 + - name: Set env.BRANCH + run: | + echo "BRANCH=$(echo $GITHUB_REF | cut -d'/' -f 3)" | tee -a $GITHUB_ENV + case ${BRANCH} in + develop) + CF_SPACE="dev" + ;; + main) + CF_SPACE="prod" + ;; + stage) + CF_SPACE="staging" + ;; + esac + echo "CF_SPACE=${CF_SPACE}" | tee -a $GITHUB_ENV + - name: Install basic dependancies + run: ./scripts/pipeline/deb-basic-deps.sh + - name: Install Cloudfoundry CLI + run: ./scripts/pipeline/deb-cf-install.sh + - name: Cloud.gov login + env: + CF_USER: "${{ secrets.CF_USER }}" + CF_PASSWORD: "${{ secrets.CF_PASSWORD }}" + CF_ORG: "${{ secrets.CF_ORG }}" + PROJECT: "${{ secrets.PROJECT }}" + run: source ./scripts/pipeline/cloud-gov-login.sh + - name: Start Bastion + env: + DATABASE_BACKUP_BASTION_NAME: "${{ secrets.DATABASE_BACKUP_BASTION_NAME }}" + PROJECT: "${{ secrets.PROJECT }}" + run: | + cf start "${PROJECT}-${DATABASE_BACKUP_BASTION_NAME}-${CF_SPACE}" >/dev/null 2>&1 + ./scripts/pipeline/cloud-gov-wait-for-app-start.sh "${PROJECT}-${DATABASE_BACKUP_BASTION_NAME}-${CF_SPACE}" + restore-database: + runs-on: ubuntu-latest + needs: start-bastion + steps: + - name: Checkout + uses: actions/checkout@v6.0.2 + - name: Set env.BRANCH + run: | + echo "BRANCH=$(echo $GITHUB_REF | cut -d'/' -f 3)" | tee -a $GITHUB_ENV + + case ${BRANCH} in + develop) + CF_SPACE="dev" + ;; + main) + CF_SPACE="prod" + ;; + stage) + CF_SPACE="staging" + ;; + esac + echo "CF_SPACE=${CF_SPACE}" | tee -a $GITHUB_ENV + - name: Install basic dependancies + run: ./scripts/pipeline/deb-basic-deps.sh + - name: Install AWSCLI + run: ./scripts/pipeline/awscli-install.sh + - name: Install MySQL Client + run: ./scripts/pipeline/deb-mysql-client-install.sh + - name: Install Cloudfoundry CLI + run: ./scripts/pipeline/deb-cf-install.sh + - name: Cloud.gov login + env: + CF_USER: "${{ secrets.CF_USER }}" + CF_PASSWORD: "${{ secrets.CF_PASSWORD }}" + CF_ORG: "${{ secrets.CF_ORG }}" + PROJECT: "${{ secrets.PROJECT }}" + run: source ./scripts/pipeline/cloud-gov-login.sh + - name: Restore database + shell: bash + env: + CF_USER: "${{ secrets.CF_USER }}" + CF_PASSWORD: "${{ secrets.CF_PASSWORD }}" + CF_ORG: "${{ secrets.CF_ORG }}" + PROJECT: "${{ secrets.PROJECT }}" + DATABASE_BACKUP_BASTION_NAME: "${{ secrets.DATABASE_BACKUP_BASTION_NAME }}" + run: | + export S3_FILE_PATH=${{ github.event.inputs.database_file_override }} + source ./scripts/pipeline/s3-backup-download.sh + source ./scripts/pipeline/database-restore.sh + source ./scripts/pipeline/cloud-gov-post-deploy.sh + stop-bastion: + name: Stop Bastion + runs-on: ubuntu-latest + needs: restore-database + if: ${{ always() }} + steps: + - name: Checkout + uses: actions/checkout@v6.0.2 + - name: Set env.BRANCH + run: | + echo "BRANCH=$(echo $GITHUB_REF | cut -d'/' -f 3)" | tee -a $GITHUB_ENV + case ${BRANCH} in + develop) + CF_SPACE="dev" + ;; + main) + CF_SPACE="prod" + ;; + stage) + CF_SPACE="staging" + ;; + esac + echo "CF_SPACE=${CF_SPACE}" | tee -a $GITHUB_ENV + - name: Install basic dependancies + run: ./scripts/pipeline/deb-basic-deps.sh + - name: Install Cloudfoundry CLI + run: ./scripts/pipeline/deb-cf-install.sh + - name: Cloud.gov login + env: + CF_USER: "${{ secrets.CF_USER }}" + CF_PASSWORD: "${{ secrets.CF_PASSWORD }}" + CF_ORG: "${{ secrets.CF_ORG }}" + PROJECT: "${{ secrets.PROJECT }}" + run: source ./scripts/pipeline/cloud-gov-login.sh + - name: Stop Bastion + env: + DATABASE_BACKUP_BASTION_NAME: "${{ secrets.DATABASE_BACKUP_BASTION_NAME }}" + PROJECT: "${{ secrets.PROJECT }}" + run: cf stop "${PROJECT}-${DATABASE_BACKUP_BASTION_NAME}-${CF_SPACE}" >/dev/null 2>&1 diff --git a/scripts/cloud-gov-s3-creds.sh b/scripts/cloud-gov-s3-creds.sh index a5e7a1dc..28afbf51 100755 --- a/scripts/cloud-gov-s3-creds.sh +++ b/scripts/cloud-gov-s3-creds.sh @@ -3,12 +3,19 @@ ## Get current username with no special characters. user=$(whoami | tr -dc '[:alnum:]\n\r' | tr '[:upper:]' '[:lower:]') -if [ -z "${bucket_name}" ]; then - echo -e "Error: No bucket name is set!\n" - echo -e "Export the bucket name, found with 'cf services | grep s3', then run the script again." - echo -e "This is the name of the bucket in cloud.gov, not the AWS bucket name.\n" - echo -e "Example usage:\n\texport bucket_name=project-bucket-name-environment\n\t$0\n" - return +## Find the CF s3 service name. Look on the command line, then in the environment. +cf_service=$1 +if [ -z "${cf_service}" ]; then + cf_service="${cf_s3_service}" + if [ -z "${cf_service}" ]; then + echo -e "Error: No CF s3 service name is set!\n" + echo -e "Export the CF s3 service name, found with 'cf services | grep s3', then run the script again." + echo -e "This is the name of the bucket in cloud.gov, not the AWS CF s3 service name.\n" + echo -e "Example usage:\n\texport cf_service=project-bucket-name-environment\n\t$0\n" + return + fi +else + shift fi if [ -n "$(cf orgs | grep FAILED)" ]; then @@ -17,18 +24,18 @@ fi echo "Deleting old credentials..." { - service_key="${bucket_name}-${user}-key" + service_key="${cf_service}-${user}-key" ## Delete any old keys. - cf delete-service-key "${bucket_name}" "${service_key}" -f + cf delete-service-key "${cf_service}" "${service_key}" -f } >/dev/null 2>&1 [ "${1}" = "-d" ] && echo "AWS bucket key deleted." && return echo "Creating key..." { - cf create-service-key "${bucket_name}" "${service_key}" - s3_credentials=$(cf service-key "${bucket_name}" "${service_key}" | tail -n +2) + cf create-service-key "${cf_service}" "${service_key}" + s3_credentials=$(cf service-key "${cf_service}" "${service_key}" | tail -n +2) aws_access_key=$(echo "${s3_credentials}" | jq -r '.credentials.access_key_id') aws_bucket_name=$(echo "${s3_credentials}" | jq -r '.credentials.bucket') aws_bucket_region=$(echo "${s3_credentials}" | jq -r '.credentials.region') diff --git a/scripts/download_backup.sh b/scripts/download_backup.sh index e37b7132..74ac1789 100755 --- a/scripts/download_backup.sh +++ b/scripts/download_backup.sh @@ -58,8 +58,7 @@ fi help(){ echo "Usage: $0 [options]" >&2 echo - echo " -b The name of the S3 bucket with the backup." - echo " -e Environment of backup to download." + echo " -c The name of the CF service containing the S3 bucket with the backup." echo " -s Name of the space the backup bucket is in." echo " -d Date to retrieve backup from. Acceptable values are 'latest' or in 'YYYY-MM-DD' format and no @@ -69,45 +68,34 @@ help(){ RED='\033[0;31m' NC='\033[0m' -while getopts 'b:e:s:d:' flag; do +while getopts 'c:e:s:d:' flag; do case ${flag} in - b) backup_bucket=${OPTARG} ;; - e) env=${OPTARG} ;; + c) cf_s3_service=${OPTARG} ;; s) space=${OPTARG} ;; d) retrieve_date=${OPTARG} ;; *) help && exit 1 ;; esac done -[[ -z "${backup_bucket}" ]] && help && echo -e "\n${RED}Error: Missing -b flag.${NC}" && exit 1 -[[ -z "${env}" ]] && help && echo -e "\n${RED}Error: Missing -e flag.${NC}" && exit 1 +[[ -z "${cf_s3_service}" ]] && help && echo -e "\n${RED}Error: Missing -b flag.${NC}" && exit 1 [[ -z "${space}" ]] && help && echo -e "\n${RED}Error: Missing -s flag.${NC}" && exit 1 [[ -z "${retrieve_date}" ]] && help && echo -e "\n${RED}Error: Missing -d flag.${NC}" && exit 1 -echo "Getting backup bucket credentials..." +echo "Getting backup bucket credentials for $cf_s3_service in $space ..." { cf target -s "${space}" + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + . "${script_dir}/cloud-gov-s3-creds.sh" ${cf_s3_service} + export bucket="${AWS_BUCKET}" +} &>/dev/null +[ $? -ne 0 ] && echo -e "\n${RED}Error: Failed to get backup bucket credentials.${NC}" && exit 1 - export service="${backup_bucket}" - export service_key="${service}-key" - cf delete-service-key "${service}" "${service_key}" -f - cf create-service-key "${service}" "${service_key}" - sleep 2 - export s3_credentials=$(cf service-key "${service}" "${service_key}" | tail -n +2) - - export AWS_ACCESS_KEY_ID=$(echo "${s3_credentials}" | jq -r '.credentials.access_key_id') - export bucket=$(echo "${s3_credentials}" | jq -r '.credentials.bucket') - export AWS_DEFAULT_REGION=$(echo "${s3_credentials}" | jq -r '.credentials.region') - export AWS_SECRET_ACCESS_KEY=$(echo "${s3_credentials}" | jq -r '.credentials.secret_access_key') - -} >/dev/null 2>&1 - -echo "Downloading backup..." +echo "Downloading backup from $bucket..." { - - aws s3 cp s3://${bucket}/${env}/${retrieve_date}.tar.gz . --no-verify-ssl 2>/dev/null - cf delete-service-key "${service}" "${service_key}" -f - -} >/dev/null 2>&1 + aws s3 cp s3://${bucket}/${retrieve_date}.sql.gz . --no-verify-ssl + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + . "${script_dir}/cloud-gov-s3-creds.sh" -d +} &>/dev/null +[ $? -ne 0 ] && echo -e "\n${RED}Error: Failed to download backup from S3 bucket.${NC}" && exit 1 echo "File saved: ${retrieve_date}.tar.gz"