Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
bradyvercher committed Jul 9, 2015
0 parents commit e807ae9
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 [email protected]: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)
21 changes: 21 additions & 0 deletions deploy.yml
Original file line number Diff line number Diff line change
@@ -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 }
60 changes: 60 additions & 0 deletions group_vars/testing
Original file line number Diff line number Diff line change
@@ -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:
24 changes: 24 additions & 0 deletions roles/deploy/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions roles/deploy/tasks/build.yml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions roles/deploy/tasks/deploy.yml
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions roles/deploy/tasks/log.yml
Original file line number Diff line number Diff line change
@@ -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 != ""
6 changes: 6 additions & 0 deletions roles/deploy/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- include: setup.yml
- include: build.yml
- include: shared.yml
- include: deploy.yml
- include: log.yml
8 changes: 8 additions & 0 deletions roles/deploy/tasks/setup.yml
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions roles/deploy/tasks/shared.yml
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions slack-command-handler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
/**
* Slack slash command handler.
*
* Receives a payload from a slash command and calls an Ansible playbook. This
* should be placed in a web-accessible location and registered with Slack.
*
* @link https://api.slack.com/slash-commands
*/

define( 'SLACK_TOKEN', '' );
define( 'ANSIBLE_INVENTORY_PATH', '/srv/www/ansible' );
define( 'ANSIBLE_PLAYBOOK_PATH', '/srv/www/ansible' );

// Ensure this is a POST request with a valid token.
if ( empty( $_POST['token'] ) || SLACK_TOKEN !== $_POST['token'] ) {
exit( 1 );
}

// @todo Consider validating users here.

list( $environment, $branch ) = explode( ' ', $_POST['text'] );

// Validate the environment.
$environments = array( 'production', 'staging' );
if ( ! in_array( $environment, $environments ) ) {
exit( 'Invalid environment. Choose `production` or `staging`.' );
}

// Sanitize the branch to deploy.
if ( 'production' === $environment ) {
$branch = 'production';
} elseif ( empty( $branch ) ) {
$branch = 'staging';
}

// Connection details are managed in Ansible inventory.
$command = sprintf(
'sudo -H -u deploy /usr/bin/ansible-playbook -i %1$s/%2$s%3$s %4$s/deploy.yml',
rtrim( ANSIBLE_INVENTORY_PATH, '/' )
$environment,
'staging' === $environment ? ' -c local' : '',
rtrim( ANSIBLE_PLAYBOOK_PATH, '/' )
);

set_time_limit( 0 );
exec( escapeshellcmd( $command ), $output );
exit( 0 );
5 changes: 5 additions & 0 deletions testing
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[webservers]
localhost ansible_connection=local

[testing:children]
webservers

0 comments on commit e807ae9

Please sign in to comment.