From 3ccfe51727c35f7cef25216f0ae4d0701d9dabb9 Mon Sep 17 00:00:00 2001 From: EmlynK Date: Thu, 20 Jan 2022 10:13:40 +0000 Subject: [PATCH 1/5] Defer nightly backups, disable ASG processes during syncs and run syncs with backup (#94) * Change location of nightly backup script and delegate the cron that runs it to the deploy server, if required (for ASGs). * Set up nightly backup crons in separate files in /etc/cron.d * Can't put site cron files in /etc/cron.d because the deploy user doesn't have perms. * Try and add the ability to sync a site using a nightly backup instead of a fresh DB dump. * Use Ansible modules to look up RDS host and to copy the nightly backup into place. * Delegate PATH setup in db backup cron to localhost. * Shell bad. Command good. But makes it convoluted. Oh well. * Used wrong database name in source database copy. * Try and disable the ReplaceUnhealthy auto scale process during syncs. * Fix Drupal cron roles when deferring to deploy server. --- .../cron_database_backup-mysql/tasks/main.yml | 10 +++ .../tasks/setup.yml | 18 ++++- roles/cron/cron_drupal7/tasks/main.yml | 1 + roles/cron/cron_drupal8/tasks/main.yml | 1 + .../database_sync-mysql/defaults/main.yml | 9 +++ .../database_sync-mysql/tasks/sync.yml | 79 ++++++++++++++++++- 6 files changed, 115 insertions(+), 3 deletions(-) diff --git a/roles/cron/cron_database_backup/cron_database_backup-mysql/tasks/main.yml b/roles/cron/cron_database_backup/cron_database_backup-mysql/tasks/main.yml index 04789df8..ffd14157 100644 --- a/roles/cron/cron_database_backup/cron_database_backup-mysql/tasks/main.yml +++ b/roles/cron/cron_database_backup/cron_database_backup-mysql/tasks/main.yml @@ -3,6 +3,16 @@ # screwing the daily backup when using rolling db, we could # add a keep mechanism for backup scripts, like for the dumps themselves. # Nice to have more than anything. +- name: Setup PATH in crontab. + cron: + name: PATH + env: true + job: "/usr/bin:/usr/local/bin:/bin:/home/{{ deploy_user }}/.bin" + delegate_to: localhost + when: + - drupal.defer is defined + - drupal.defer + - include_tasks: setup.yml vars: database: database diff --git a/roles/cron/cron_database_backup/cron_database_backup-mysql/tasks/setup.yml b/roles/cron/cron_database_backup/cron_database_backup-mysql/tasks/setup.yml index bdc1534c..40b0c47e 100644 --- a/roles/cron/cron_database_backup/cron_database_backup-mysql/tasks/setup.yml +++ b/roles/cron/cron_database_backup/cron_database_backup-mysql/tasks/setup.yml @@ -25,12 +25,26 @@ - name: Create backup script. template: src: "regular-backups.sh.j2" - dest: "/home/{{ deploy_user }}/{{ database.host }}-{{ database.original.database }}-regular-backups.sh" + dest: "/home/{{ deploy_user }}/shared/{{ project_name }}_{{ build_type }}/{{ database.host }}-{{ database.original.database }}-regular-backups.sh" mode: 0700 +- name: Define backup cron job command. + set_fact: + _backup_cron_job_command: "/bin/sh /home/{{ deploy_user }}/shared/{{ project_name }}_{{ build_type }}/{{ database.host }}-{{ database.original.database }}-regular-backups.sh" + +- name: Define backup cron job command if deferred (ASG). + set_fact: + _backup_cron_job_command: "cd {{ _ce_deploy_base_dir }} && {{ _ce_deploy_ansible_location }} {{ drupal.defer_target }} -m shell -a \"{{ _backup_cron_job_command }}\"" + when: + - drupal.defer is defined + - drupal.defer + - drupal.defer_target is defined + - drupal.defer_target | length > 0 + - name: Setup regular backup for MySQL. cron: name: "cron_mysql_{{ database.host }}_{{ database.original.database }}" minute: "{{ _cron_mysql_backup_minute }}" hour: "{{ _cron_mysql_backup_hour }}" - job: "/bin/sh /home/{{ deploy_user }}/{{ database.host }}-{{ database.original.database }}-regular-backups.sh" + job: "{{ _backup_cron_job_command }}" + delegate_to: "{{ 'localhost' if drupal.defer else inventory_hostname }}" diff --git a/roles/cron/cron_drupal7/tasks/main.yml b/roles/cron/cron_drupal7/tasks/main.yml index d0cbfce9..75fa05c7 100644 --- a/roles/cron/cron_drupal7/tasks/main.yml +++ b/roles/cron/cron_drupal7/tasks/main.yml @@ -4,6 +4,7 @@ name: PATH env: true job: "/usr/bin:/usr/local/bin:/bin:/home/{{ deploy_user }}/.bin" + delegate_to: localhost when: - drupal.defer is defined - drupal.defer diff --git a/roles/cron/cron_drupal8/tasks/main.yml b/roles/cron/cron_drupal8/tasks/main.yml index d0cbfce9..75fa05c7 100644 --- a/roles/cron/cron_drupal8/tasks/main.yml +++ b/roles/cron/cron_drupal8/tasks/main.yml @@ -4,6 +4,7 @@ name: PATH env: true job: "/usr/bin:/usr/local/bin:/bin:/home/{{ deploy_user }}/.bin" + delegate_to: localhost when: - drupal.defer is defined - drupal.defer diff --git a/roles/sync/database_sync/database_sync-mysql/defaults/main.yml b/roles/sync/database_sync/database_sync-mysql/defaults/main.yml index c1d7b259..48e3e4a0 100644 --- a/roles/sync/database_sync/database_sync-mysql/defaults/main.yml +++ b/roles/sync/database_sync/database_sync-mysql/defaults/main.yml @@ -15,6 +15,13 @@ mysql_sync: type: fixed # For "rolling builds", so we can compute the database name. build_id: mybuildprod + # Whether or not use to create a fresh database backup or use a nightly one. + fresh_db: true + # Location where nightly backups are kept. This must match the value set for cron_mysql_backup.dumps_directory. Below is the default. + # This var is only used when fresh_db is set to "false". + dumps_directory: "/home/{{ deploy_user }}/shared/{{ project_name }}_{{ build_type }}/db_backups/mysql/regular" + # If the source is on an ASG, provide the ASG name here. Otherwise, leave empty. + asg: "" target: database: "{{ project_name }}_dev" credentials_file: "/home/{{ deploy_user }}/.mysql.creds" @@ -25,3 +32,5 @@ mysql_sync: type: fixed # For "rolling builds", so we can compute the database name. build_id: mybuilddev + # If the target is on an ASG, provide the ASG name here. Otherwise, leave empty. + asg: "" diff --git a/roles/sync/database_sync/database_sync-mysql/tasks/sync.yml b/roles/sync/database_sync/database_sync-mysql/tasks/sync.yml index 81e9b2ce..b8182bc3 100644 --- a/roles/sync/database_sync/database_sync-mysql/tasks/sync.yml +++ b/roles/sync/database_sync/database_sync-mysql/tasks/sync.yml @@ -1,4 +1,40 @@ --- +- name: Get database source host region. + amazon.aws.ec2_metadata_facts: + register: mysql_sync_source_database_host_info + delegate_to: "{{ database.source.host }}" + when: + - database.source.asg is defined + - database.source.asg | length > 0 + - database.source.fresh_db is defined + - database.source.fresh_db + +# This task does not need a delegate_to because the hosts set in the sync playbook in the repo should be the target host. +- name: Get database target host region. + amazon.aws.ec2_metadata_facts: + register: mysql_sync_target_database_host_info + when: + - database.target.asg is defined + - database.target.asg | length > 0 + +- name: Disable ReplaceUnhealthy autoscale process on source ASG. + ansible.builtin.command: > + aws autoscaling suspend-processes --auto-scaling-group-name {{ database.source.asg }} --scaling-processes ReplaceUnhealthy --region {{ mysql_sync_source_database_host_info.ansible_facts.ansible_ec2_instance_identity_document_region }} + delegate_to: localhost + when: + - database.source.asg is defined + - database.source.asg | length > 0 + - database.source.fresh_db is defined + - database.source.fresh_db + +- name: Disable ReplaceUnhealthy autoscale process on target ASG. + ansible.builtin.command: > + aws autoscaling suspend-processes --auto-scaling-group-name {{ database.target.asg }} --scaling-processes ReplaceUnhealthy --region {{ mysql_sync_target_database_host_info.ansible_facts.ansible_ec2_instance_identity_document_region }} + delegate_to: localhost + when: + - database.target.asg is defined + - database.target.asg | length > 0 + - name: Register remote dump name (from database). set_fact: mysql_sync_source_dump_path: "/tmp/{{ database.source.database }}.sql.bz2" @@ -33,7 +69,30 @@ - name: Take a dump from source database. shell: "mysqldump --defaults-extra-file={{ database.source.credentials_file }} {{ mysql_sync_source_database }} | bzip2 > {{ mysql_sync_source_dump_path }}" delegate_to: "{{ database.source.host }}" - when: not database.source.type == 'dump' + when: + - not database.source.type == 'dump' + - database.source.fresh_db + +- name: Find source database host. + ansible.builtin.command: + cmd: "grep 'host' {{ database.source.credentials_file }}" + register: mysql_host_info_grep + delegate_to: "{{ database.source.host }}" + when: not database.source.fresh_db + +- name: Register source database host. + set_fact: + mysql_sync_source_database_host: "{{ mysql_host_info_grep.stdout.split('=')[1] }}" + delegate_to: "{{ database.source.host }}" + when: not database.source.fresh_db + +- name: Copy a nightly backup for the source database. + ansible.builtin.copy: + src: "{{ database.source.dumps_directory }}/{{ mysql_sync_source_database_host }}/{{ database.source.database }}" + dest: "{{ mysql_sync_source_dump_path }}" + remote_src: true + delegate_to: "{{ database.source.host }}" + when: not database.source.fresh_db - name: Register tmp target dump name. set_fact: @@ -95,3 +154,21 @@ path: "{{ mysql_sync_target_dump_path }}" state: absent when: not database.target.type == 'dump' + +- name: Enable all autoscale processes on source ASG. + ansible.builtin.command: > + aws autoscaling resume-processes --auto-scaling-group-name {{ database.source.asg }} --region {{ mysql_sync_source_database_host_info.ansible_facts.ansible_ec2_instance_identity_document_region }} + delegate_to: localhost + when: + - database.source.asg is defined + - database.source.asg | length > 0 + - database.source.fresh_db is defined + - database.source.fresh_db + +- name: Enable all autoscale processes on target ASG. + ansible.builtin.command: > + aws autoscaling resume-processes --auto-scaling-group-name {{ database.target.asg }} --region {{ mysql_sync_target_database_host_info.ansible_facts.ansible_ec2_instance_identity_document_region }} + delegate_to: localhost + when: + - database.target.asg is defined + - database.target.asg | length > 0 From a54c09cb0ce5b6889e672ba8ea741b71a3da034b Mon Sep 17 00:00:00 2001 From: Dionisio Date: Thu, 10 Mar 2022 14:16:13 +0100 Subject: [PATCH 2/5] Added deploy.yml examples for Drupal 9 and Localgov. Updated tests (#97) --- .github/workflows/ce-deploy-test.yml | 22 ++++++++ ce-dev/ansible/examples/drupal9/deploy.yml | 55 +++++++++++++++++++ ce-dev/ansible/examples/localgov/deploy.yml | 59 +++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 ce-dev/ansible/examples/drupal9/deploy.yml create mode 100644 ce-dev/ansible/examples/localgov/deploy.yml diff --git a/.github/workflows/ce-deploy-test.yml b/.github/workflows/ce-deploy-test.yml index baa13347..141fc8e7 100644 --- a/.github/workflows/ce-deploy-test.yml +++ b/.github/workflows/ce-deploy-test.yml @@ -59,6 +59,28 @@ jobs: curl https://www.test.local shell: bash + - name: Run a test ce-dev deploy with Drupal 9 + run: | + ce-dev create -p testnine -t drupal9 -d ~/testnine + cd ~/testnine + ce-dev init + ce-dev start + ce-dev provision + ce-dev deploy + curl https://www.testnine.local + shell: bash + + - name: Run a test ce-dev deploy with Localgov + run: | + ce-dev create -p testlocalgov -t localgov -d ~/testlocalgov + cd ~/testlocalgov + ce-dev init + ce-dev start + ce-dev provision + ce-dev deploy + curl https://www.testlocalgov.local + shell: bash + # Builds the table of contents for the docs - name: Documentation (build table of contents) if: ${{ github.event.pull_request.base.ref == '1.x' }} diff --git a/ce-dev/ansible/examples/drupal9/deploy.yml b/ce-dev/ansible/examples/drupal9/deploy.yml new file mode 100644 index 00000000..deeef9ee --- /dev/null +++ b/ce-dev/ansible/examples/drupal9/deploy.yml @@ -0,0 +1,55 @@ +--- +# Template playbook for a local Drupal9 codebase. +- hosts: deploy-web + vars: + - project_name: example + - project_type: drupal8 + - webroot: web + - build_type: local + - _env_type: dev + - _domain_name: www.{{ project_name }}.local + # Path to your project root. This must match the "volume" set in the docker-compose template. + - deploy_path: /home/ce-dev/deploy/live.local + # This actually does not take any backup, but is needed to populate settings.php. + - mysql_backup: + handling: none + credentials_handling: static + # A list of Drupal sites (for multisites). + - drupal: + sites: + - folder: "default" + public_files: "sites/default/files" + install_command: "-y si" + # Toggle config import on/off. Disabled for initial passes. + config_import_command: "" + # config_import_command: "cim" + config_sync_directory: "config/sync" + sanitize_command: "sql-sanitize" + # Remove after initial pass, to avoid reinstalling Drupal. + force_install: yes + base_url: "https://{{ _domain_name }}" + # Composer command to run. + - composer: + command: install + no_dev: no + working_dir: "{{ deploy_path }}" + apcu_autoloader: no + pre_tasks: + # You can safely remove these steps once you have a working composer.json. + - name: Download composer file. + get_url: + url: https://raw.githubusercontent.com/drupal/recommended-project/9.3.x/composer.json + dest: "{{ deploy_path }}/composer.json" + force: no + - name: Install drush. + command: + cmd: composer require drush/drush:11.* + chdir: "{{ deploy_path }}" + roles: + - _init # Sets some variables the deploy scripts rely on. + - composer # Composer install step. + - database_backup # This is still needed to generate credentials. + - config_generate # Generates settings.php + # - sync/database_sync # Grab database from a remote server. + - database_apply # Run drush updb and config import. + - _exit # Some common housekeeping. \ No newline at end of file diff --git a/ce-dev/ansible/examples/localgov/deploy.yml b/ce-dev/ansible/examples/localgov/deploy.yml new file mode 100644 index 00000000..9844206b --- /dev/null +++ b/ce-dev/ansible/examples/localgov/deploy.yml @@ -0,0 +1,59 @@ +--- +# Template playbook for a local localgov codebase. +- hosts: deploy-web + vars: + - project_name: example + - project_type: drupal8 + - webroot: web + - build_type: local + - _env_type: dev + - _domain_name: www.{{ project_name }}.local + # Path to your project root. This must match the "volume" set in the docker-compose template. + - deploy_path: /home/ce-dev/deploy/live.local + # This actually does not take any backup, but is needed to populate settings.php. + - mysql_backup: + handling: none + credentials_handling: static + # A list of Drupal sites (for multisites). + - drupal: + sites: + - folder: "default" + public_files: "sites/default/files" + install_command: "-y si localgov" + # Toggle config import on/off. Disabled for initial passes. + config_import_command: "" + # config_import_command: "cim" + config_sync_directory: "config/sync" + sanitize_command: "sql-sanitize" + # Remove after initial pass, to avoid reinstalling Drupal. + force_install: yes + base_url: "https://{{ _domain_name }}" + # Composer command to run. + - composer: + command: install + no_dev: no + working_dir: "{{ deploy_path }}" + apcu_autoloader: no + pre_tasks: + # You can safely remove these steps once you have a working composer.json. + - name: Download composer file. + get_url: + url: https://raw.githubusercontent.com/drupal/recommended-project/9.3.x/composer.json + dest: "{{ deploy_path }}/composer.json" + force: false + - name: Install drush. + command: + cmd: composer require drush/drush:11.* + chdir: "{{ deploy_path }}" + - name: Install localgov. + command: + cmd: composer require localgovdrupal/localgov + chdir: "{{ deploy_path }}" + roles: + - _init # Sets some variables the deploy scripts rely on. + - composer # Composer install step. + - database_backup # This is still needed to generate credentials. + - config_generate # Generates settings.php + # - sync/database_sync # Grab database from a remote server. + - database_apply # Run drush updb and config import. + - _exit # Some common housekeeping. \ No newline at end of file From 223a3b5ecec42997d9e52de690cf7b8c5f9f19ab Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Thu, 31 Mar 2022 12:43:36 +0200 Subject: [PATCH 3/5] Adding a new SimpleSAMLphp meta role. (#100) --- roles/_meta/deploy-simplesamlphp/README.md | 9 +++++++++ roles/_meta/deploy-simplesamlphp/tasks/main.yml | 17 +++++++++++++++++ .../deploy_code-simplesamlphp/tasks/main.yml | 1 + 3 files changed, 27 insertions(+) create mode 100644 roles/_meta/deploy-simplesamlphp/README.md create mode 100644 roles/_meta/deploy-simplesamlphp/tasks/main.yml create mode 100644 roles/deploy_code/deploy_code-simplesamlphp/tasks/main.yml diff --git a/roles/_meta/deploy-simplesamlphp/README.md b/roles/_meta/deploy-simplesamlphp/README.md new file mode 100644 index 00000000..3d82e671 --- /dev/null +++ b/roles/_meta/deploy-simplesamlphp/README.md @@ -0,0 +1,9 @@ +# SimpleSAMLphp +Role for deploying single SimpleSAMLphp instances. Do not use if you are deploying SimpleSAMLphp with another application like Drupal via composer. + +This role currently assumes all config is in the repository alongside composer.json and the special `SIMPLESAMLPHP_CONFIG_DIR` variable is passed in via the web server vhost to tell SimpleSAMLphp where the config is on the server. For vhost configuration in Nginx see ce-provision: + +* https://github.com/codeenigma/ce-provision/blob/1.x/roles/nginx + + + diff --git a/roles/_meta/deploy-simplesamlphp/tasks/main.yml b/roles/_meta/deploy-simplesamlphp/tasks/main.yml new file mode 100644 index 00000000..7af28fa2 --- /dev/null +++ b/roles/_meta/deploy-simplesamlphp/tasks/main.yml @@ -0,0 +1,17 @@ +--- +# Default SimpleSAMLphp role. This is suitable for a standalone SimpleSAMLphp installation + +- ansible.builtin.import_role: + name: _init +- ansible.builtin.import_role: + name: deploy_code +- ansible.builtin.import_role: + name: composer +- ansible.builtin.import_role: + name: database_backup +- ansible.builtin.import_role: + name: live_symlink +- ansible.builtin.import_role: + name: cache_clear/cache_clear-opcache +- ansible.builtin.import_role: + name: _exit diff --git a/roles/deploy_code/deploy_code-simplesamlphp/tasks/main.yml b/roles/deploy_code/deploy_code-simplesamlphp/tasks/main.yml new file mode 100644 index 00000000..03c03856 --- /dev/null +++ b/roles/deploy_code/deploy_code-simplesamlphp/tasks/main.yml @@ -0,0 +1 @@ +# Nothing to do. \ No newline at end of file From 9d5b053e4a9214d6373511dcf25150e5d1e6b3e2 Mon Sep 17 00:00:00 2001 From: gregharvey Date: Mon, 11 Apr 2022 12:12:09 +0200 Subject: [PATCH 4/5] Making the MySQL dump command for routine back-ups less aggressive. --- .../templates/regular-backups.sh.j2 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/roles/cron/cron_database_backup/cron_database_backup-mysql/templates/regular-backups.sh.j2 b/roles/cron/cron_database_backup/cron_database_backup-mysql/templates/regular-backups.sh.j2 index 901cfcf7..64580c3f 100644 --- a/roles/cron/cron_database_backup/cron_database_backup-mysql/templates/regular-backups.sh.j2 +++ b/roles/cron/cron_database_backup/cron_database_backup-mysql/templates/regular-backups.sh.j2 @@ -10,7 +10,9 @@ TARBALL="$DB_NAME-$(date -Iseconds).sql.bz2" KEEP=$(({{ database.original.backup.keep | default(cron_mysql_backup.keep) }}+1)) backup(){ - mysqldump -u"$DBUSER" -p"$DBPASSWORD" -h"$DBHOST" "$CURRENT_DBNAME" | bzip2 > "$TARGET_DIR/$TARBALL" + mysqldump --max-allowed-packet=128M --single-transaction --skip-opt -e --quick \ + --skip-disable-keys --skip-add-locks -C -a --add-drop-table \ + -u"$DBUSER" -p"$DBPASSWORD" -h"$DBHOST" "$CURRENT_DBNAME" | bzip2 > "$TARGET_DIR/$TARBALL" ln -sfn "$TARGET_DIR/$TARBALL" "$TARGET_DIR/$DB_NAME" } @@ -23,4 +25,4 @@ cleanup(){ } backup -cleanup \ No newline at end of file +cleanup From 680fbd897f885347e61169a68c9b138e68084cd6 Mon Sep 17 00:00:00 2001 From: gregharvey Date: Mon, 11 Apr 2022 13:48:13 +0200 Subject: [PATCH 5/5] Making max_allowed_packets a variable we can set. --- .../cron_database_backup-mysql/defaults/main.yml | 1 + .../cron_database_backup-mysql/templates/regular-backups.sh.j2 | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/roles/cron/cron_database_backup/cron_database_backup-mysql/defaults/main.yml b/roles/cron/cron_database_backup/cron_database_backup-mysql/defaults/main.yml index c6c57269..430bff29 100644 --- a/roles/cron/cron_database_backup/cron_database_backup-mysql/defaults/main.yml +++ b/roles/cron/cron_database_backup/cron_database_backup-mysql/defaults/main.yml @@ -2,3 +2,4 @@ cron_mysql_backup: dumps_directory: "/home/{{ deploy_user }}/shared/{{ project_name }}_{{ build_type }}/db_backups/mysql/regular" keep: 10 + max_allowed_packets: 128M # Needs to be larger than the single largest database row diff --git a/roles/cron/cron_database_backup/cron_database_backup-mysql/templates/regular-backups.sh.j2 b/roles/cron/cron_database_backup/cron_database_backup-mysql/templates/regular-backups.sh.j2 index 64580c3f..ecae0457 100644 --- a/roles/cron/cron_database_backup/cron_database_backup-mysql/templates/regular-backups.sh.j2 +++ b/roles/cron/cron_database_backup/cron_database_backup-mysql/templates/regular-backups.sh.j2 @@ -10,7 +10,7 @@ TARBALL="$DB_NAME-$(date -Iseconds).sql.bz2" KEEP=$(({{ database.original.backup.keep | default(cron_mysql_backup.keep) }}+1)) backup(){ - mysqldump --max-allowed-packet=128M --single-transaction --skip-opt -e --quick \ + mysqldump --max-allowed-packet={{ cron_mysql_backup.max_allowed_packets }} --single-transaction --skip-opt -e --quick \ --skip-disable-keys --skip-add-locks -C -a --add-drop-table \ -u"$DBUSER" -p"$DBPASSWORD" -h"$DBHOST" "$CURRENT_DBNAME" | bzip2 > "$TARGET_DIR/$TARBALL" ln -sfn "$TARGET_DIR/$TARBALL" "$TARGET_DIR/$DB_NAME"