--- - name: Deploy impress-2020 from the current local version hosts: webserver vars: skip_build: no # Can override this by running `yarn deploy-skip-build` local_app_root: "{{ playbook_dir }}/../.." remote_versions_root: "/srv/impress-2020/versions" tasks: # For our deployment, our server resources are more constrained than our # dev machine, and builds are an expensive uncommon RAM & CPU spike. Let's # use our local machine for it! (I found that, running remotely, 2GB RAM # was not enough to build impress-2020 😅) - name: Run `yarn build` locally to build the current version delegate_to: localhost command: chdir: "{{ local_app_root }}" cmd: yarn build when: not skip_build - name: Generate a version name from the current timestamp command: date '+%Y-%m-%d-%s' register: new_app_version - name: Print out the new version name debug: msg: "Deploying new version: {{ new_app_version.stdout }}" - name: Save new remote folder path to a variable set_fact: remote_app_root: "{{ remote_versions_root }}/{{ new_app_version.stdout }}" - name: Create new remote folder for the new version file: path: "{{ remote_app_root }}" state: directory - name: Copy local app's source files to new remote folder ansible.posix.synchronize: src: "{{ local_app_root }}/" dest: "{{ remote_app_root }}" rsync_opts: - "--exclude=.git" - "--filter=':- .gitignore'" - name: Copy local app's .next build files to new remote folder ansible.posix.synchronize: src: "{{ local_app_root }}/.next/" dest: "{{ remote_app_root }}/.next" - name: Copy local app's .env secrets file to new remote folder ansible.posix.synchronize: src: "{{ local_app_root }}/.env" dest: "{{ remote_app_root }}/.env" - name: Check whether we can reuse the currently deployed node_modules. # First, compare the files. Then, ensure that node_modules is in a good # state and will be linkable. Finally, ensure that it's one of the 5 most # recent versions, so it won't be cleaned up. If this succeeds, we can do # the link cheat! If this fails, proceed and run `yarn install` instead. command: | bash -c " cmp '{{ remote_app_root }}/yarn.lock' '/srv/impress-2020/current/yarn.lock' && test -e /srv/impress-2020/current/node_modules && (ls -dt /srv/impress-2020/versions/* | head -n 5 | grep $(realpath /srv/impress-2020/current/node_modules/..))" failed_when: no register: can_reuse_node_modules_when_successful - name: Link node_modules to current deployed version, if they match # Linking is a pretty wild cheat to make this much faster than copying! # We're counting on the assumptions that 1) versions' dependencies don't # change after the fact, and 2) the app doesn't mutate node_modules while # running. So, these two copies of node_modules *should* just stay # permanently the way they are forever, so linking shoouulld be safe 🤞 command: | bash -c "ln -s $(realpath /srv/impress-2020/current/node_modules) {{ remote_app_root }}/node_modules" when: "can_reuse_node_modules_when_successful.rc == 0" - name: Run `yarn install` to install dependencies in new remote folder command: chdir: "{{ remote_app_root }}" cmd: yarn install when: "can_reuse_node_modules_when_successful.rc > 0" - name: Update the `current` folder to point to the new version file: src: "{{ remote_app_root }}" dest: /srv/impress-2020/current state: link - name: Reload the app in pm2 command: pm2 reload impress-2020 - name: Find older versions to clean up # Print out all but the 5 last-recently-updated versions. # NOTE: If we change this count, change the count we use when checking # whether to reuse node_modules too! # TODO: This *will* still break *older* versions that pointed to an old # `node_modules` that got cleaned up, which could make rollback a # pain. We don't roll back often for DTI so that's probably fine, # but it'd be better to do something a bit more robust - or just # drop it and accept the install perf hit every time. (It's only # like 40 seconds on this last test, which isn't as bad as I # remember, especially when deploys aren't super common.) command: chdir: "{{ remote_versions_root }}" cmd: bash -c 'ls -t | tail -n +6' register: versions_to_clean_up - name: Clean up older versions file: path: "{{ remote_versions_root }}/{{ item }}" state: absent with_items: "{{ versions_to_clean_up.stdout_lines }}"