diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..07b36c0 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# Ansible Deployment Playbook + +An ansible playbook for deploying a WordPress site from a git repository. + +This is currently a proof of concept and likely isn't generic enough to work +for most deployment processes. Feel free to fork it and adapt to your needs. + +We're using [Composer to manage the site stack](http://composer.rarst.net/recipe/site-stack) along with a [SatisPress](https://github.com/blazersix/satispress) instance to manage private packages. Only custom code is stored in site repository. + +The repository and node modules are cached in a workspace to speed up subsequent deployments. + + +## Getting Started + +Install [Ansible](http://docs.ansible.com/intro_installation.html) then run the following commands: + +```sh +# Clone this repository. +git clone git@github.com:cedaro/ansible-deploy.git + +# Change to the cloned repository +cd ansible-deploy + +# Run the deploy playbook using the testing inventory +ansible-playbook -i testing deploy.yml +``` + +Running those commands will pull down an [example website](https://github.com/cedaro/website-example) and deploy it to your local machine at `/tmp/ansible-deploy`. + + +## Next Steps + +* Create your own inventory using `group_vars/testing` and `roles/deploy/defaults/main.yml` for guidance. +* Read through the tasks in `roles/deploy/tasks` to see what tasks are run. + + +## Slack Integration + +The `slack-command-handler.php` file serves as a quick demonstration for using a [Slack slash command](https://api.slack.com/slash-commands) to call an Ansible playbook. The handler should be placed in a web-accessible location. + +After registering the command with Slack, a deployment can be kicked off from any channel with a command similar to the following: + +``` +/deploy production master +``` + + +## Resources + +* [npm without sudo](https://github.com/sindresorhus/guides/blob/master/npm-global-without-sudo.md) + + +## To Do + +* Deploy a specific commit, branch, or tag (should already be possible with `--extra-vars`) +* Implement a rollback method +* Clean up old releases +* [Official deploy helper module](https://github.com/ansible/ansible-modules-extras/pull/110) diff --git a/deploy.yml b/deploy.yml new file mode 100644 index 0000000..e5bd499 --- /dev/null +++ b/deploy.yml @@ -0,0 +1,21 @@ +--- +- name: Deploy a repository + hosts: webservers + + pre_tasks: + - slack: + token: "{{ slack_token }}" + msg: "Deploying `{{ branch }}` to {{ inventory_hostname }}..." + when: slack_token is defined + + - set_fact: + releases_path: "{{ deploy_to }}/releases" + release_directory: "{{ lookup('pipe', 'date -u +%Y%m%d%H%M%S') }}" + + - set_fact: + build_path: "{{ workspace }}/build-{{ release_directory }}" + cache_path: "{{ workspace }}/cache" + release_path: "{{ releases_path }}/{{ release_directory }}" + + roles: + - { role: deploy } diff --git a/group_vars/testing b/group_vars/testing new file mode 100644 index 0000000..8ee89bb --- /dev/null +++ b/group_vars/testing @@ -0,0 +1,60 @@ +--- +## +# The git repository and branch to deploy. +## + +repository: https://github.com/cedaro/example-website.git +branch: master + + +## +# Path where the repository is cached and the application is built. +# +# Creates the following paths: +# +# Repository cache: {{ workspace }}/cache +# Release build: {{ workspace }}/build-20150709100000 +## + +workspace: /tmp/ansible-deploy/workspace + + +## +# Path where the application should be deployed. +# +# Creates the following paths: +# +# Active version: {{ deploy_to }}/current +# All releases: {{ deploy_to }}/releases +# Deployed release: {{ deploy_to }}/releases/20150709100000 +# Log file: {{ deploy_to }}/deploy.log +## + +deploy_to: /tmp/ansible-deploy + +## +# A list of files to exclude from the deployment. +## + +excludes: + - .editorconfig + - .git + - .gitignore + - composer.json + - composer.lock + - config + - Gruntfile.js + - node_modules + - package.json + - README.md + - wordpress/wp-content + + +## +# Add a Slack Incoming Webhook token to send notifications when a deploy begins +# and finishes. +# +# https://api.slack.com/incoming-webhooks +## + +#slack_token: diff --git a/roles/deploy/defaults/main.yml b/roles/deploy/defaults/main.yml new file mode 100644 index 0000000..91ab7db --- /dev/null +++ b/roles/deploy/defaults/main.yml @@ -0,0 +1,24 @@ +--- +respository: +branch: master + +workspace: /tmp/ansible-deploy/workspace +deploy_to: /tmp/ansible-deploy/deploy_to + +composer_no_dev: yes +grunt_path: grunt +grunt_task: "" +slack_token: "" + +excludes: [] +symlinks: [] + +default_symlinks: + - src: shared/object-cache.php + dest: content/object-cache.php + - src: shared/blogs.dir + dest: content/blogs.dir + - src: shared/cache + dest: content/cache + - src: shared/uploads + dest: content/uploads diff --git a/roles/deploy/tasks/build.yml b/roles/deploy/tasks/build.yml new file mode 100644 index 0000000..cfde5f3 --- /dev/null +++ b/roles/deploy/tasks/build.yml @@ -0,0 +1,41 @@ +--- +- name: Clone the repository + git: + repo="{{ repository }}" + dest="{{ cache_path }}" + accept_hostkey=yes + version={{ branch }} + force=yes + register: git_clone_result + +- name: Install npm dependencies + npm: + path="{{ cache_path }}" + production=yes + +- name: Create the build directory + file: path="{{ build_path }}" state=directory + +- name: Copy the project to the build directory + synchronize: + src="{{ cache_path }}/" + dest="{{ build_path }}/" + rsync_opts=--exclude=.git + delegate_to: "{{ inventory_hostname }}" + +- name: Install Composer dependencies + composer: + command=install + working_dir="{{ build_path }}" + no_dev={{ composer_no_dev }} + optimize_autoloader=yes + +- name: Run Grunt build task + command: "{{ grunt_path }} {{ grunt_task }}" + args: + chdir: "{{ build_path }}" + when: grunt_task != "" + +- name: Remove excluded files + file: path="{{ build_path }}/{{ item }}" state=absent + with_items: excludes diff --git a/roles/deploy/tasks/deploy.yml b/roles/deploy/tasks/deploy.yml new file mode 100644 index 0000000..d6382a3 --- /dev/null +++ b/roles/deploy/tasks/deploy.yml @@ -0,0 +1,8 @@ +- name: Move the build directory to the release path + command: "mv {{ build_path }} {{ release_path }}" + +- name: Make it live + file: + src="{{ release_path }}" + path="{{ deploy_to }}/current" + state=link diff --git a/roles/deploy/tasks/log.yml b/roles/deploy/tasks/log.yml new file mode 100644 index 0000000..76d16b1 --- /dev/null +++ b/roles/deploy/tasks/log.yml @@ -0,0 +1,11 @@ +--- +- name: Log the deployment + lineinfile: + dest="{{ deploy_to}}/deploy.log" + line="{{ ansible_date_time.iso8601 }} - Deployed {{ branch }} (commit {{ git_clone_result.after }}) to {{ release_path }} on {{ inventory_hostname }}" + create=yes + +- slack: + token: "{{ slack_token }}" + msg: "Finished deploying `{{ branch }}` to {{ inventory_hostname }}." + when: slack_token != "" diff --git a/roles/deploy/tasks/main.yml b/roles/deploy/tasks/main.yml new file mode 100644 index 0000000..baf86d4 --- /dev/null +++ b/roles/deploy/tasks/main.yml @@ -0,0 +1,6 @@ +--- +- include: setup.yml +- include: build.yml +- include: shared.yml +- include: deploy.yml +- include: log.yml diff --git a/roles/deploy/tasks/setup.yml b/roles/deploy/tasks/setup.yml new file mode 100644 index 0000000..583a963 --- /dev/null +++ b/roles/deploy/tasks/setup.yml @@ -0,0 +1,8 @@ +- name: Ensure the workspace exists + file: path="{{ workspace }}" state=directory + +- name: Ensure the releases directory exists + file: path="{{ releases_path }}" state=directory + +- name: Ensure the shared directory exists + file: path="{{ deploy_to }}/shared" state=directory diff --git a/roles/deploy/tasks/shared.yml b/roles/deploy/tasks/shared.yml new file mode 100644 index 0000000..9a6d9f7 --- /dev/null +++ b/roles/deploy/tasks/shared.yml @@ -0,0 +1,24 @@ +--- +- name: Check for wp-config.php in shared directory + stat: path="{{ deploy_to }}/shared/wp-config.php" + register: config_stat + +- name: Copy wp-config.php to the build directory + synchronize: + src="{{ deploy_to }}/shared/wp-config.php" + dest="{{ build_path }}/wp-config.php" + delegate_to: "{{ inventory_hostname }}" + when: config_stat.stat.exists + +- name: Check for shared files and directories + stat: path="{{ deploy_to }}/{{ item.src }}" + register: shared_stat + with_items: default_symlinks | union(symlinks) + +- name: Create shared symlinks + file: + src="{{ deploy_to }}/{{ item.item.src }}" + path="{{ build_path }}/{{ item.item.dest }}" + state=link + when: item.stat.exists + with_items: shared_stat.results diff --git a/slack-command-handler.php b/slack-command-handler.php new file mode 100644 index 0000000..1c6bbe8 --- /dev/null +++ b/slack-command-handler.php @@ -0,0 +1,48 @@ +