├── test ├── roles │ └── local-ansistrano ├── main.yml ├── tasks │ ├── create-internal-paths.yml │ └── create-shared-paths.yml ├── svn.yml ├── download.yml ├── git.yml └── rsync.yml ├── ansible.cfg ├── docs ├── figure.md └── ansistrano-flow.png ├── example ├── my-playbook │ ├── ansible.cfg │ ├── rollback.yml │ ├── hosts │ └── deploy.yml └── my-app │ └── index.html ├── tasks ├── empty.yml ├── cleanup.yml ├── update-code │ ├── s3_unarchive.yml │ ├── copy_unarchive.yml │ ├── download_unarchive.yml │ ├── copy.yml │ ├── unarchive.yml │ ├── download.yml │ ├── s3.yml │ ├── rsync.yml │ ├── svn.yml │ └── git.yml ├── anon-stats.yml ├── symlink.yml ├── update-code.yml ├── setup.yml ├── rsync-deploy.yml ├── main.yml └── symlink-shared.yml ├── TESTING.md ├── Vagrantfile ├── .travis.yml ├── meta └── main.yml ├── LICENSE ├── .gitignore ├── defaults └── main.yml └── README.md /test/roles/local-ansistrano: -------------------------------------------------------------------------------- 1 | ../../. -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | roles_path = ../ -------------------------------------------------------------------------------- /docs/figure.md: -------------------------------------------------------------------------------- 1 | http://yuml.me/edit/5473b608 2 | -------------------------------------------------------------------------------- /example/my-playbook/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | host_key_checking=False -------------------------------------------------------------------------------- /docs/ansistrano-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/540/deploy/master/docs/ansistrano-flow.png -------------------------------------------------------------------------------- /example/my-app/index.html: -------------------------------------------------------------------------------- 1 | This is the main file of the project, I know it's not too much, but it will :) -------------------------------------------------------------------------------- /test/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: rsync.yml 3 | - include: git.yml 4 | - include: download.yml 5 | - include: svn.yml 6 | -------------------------------------------------------------------------------- /tasks/empty.yml: -------------------------------------------------------------------------------- 1 | # This file is intentionally left empty and it is used in those Capistrano flow steps 2 | # where you don't need to execute any custom tasks -------------------------------------------------------------------------------- /example/my-playbook/rollback.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Rollback example app to my-server.com 3 | hosts: all 4 | vars: 5 | ansistrano_deploy_to: "/tmp/my-app.com" 6 | roles: 7 | - { role: carlosbuenosvinos.ansistrano-rollback } -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | Testing locally with Ansible 2.x 2 | ================================ 3 | 4 | git clone git@github.com:ansistrano/deploy.git 5 | cd deploy 6 | vagrant up 7 | cd example/my-playbook 8 | ansible-playbook deploy.yml -i hosts -------------------------------------------------------------------------------- /tasks/cleanup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Clean up releases 3 | - name: ANSISTRANO | Clean up releases 4 | shell: ls -1dt {{ ansistrano_releases_path.stdout }}/* | tail -n +{{ ansistrano_keep_releases | int + 1 }} | xargs rm -rf 5 | when: ansistrano_keep_releases > 0 6 | -------------------------------------------------------------------------------- /tasks/update-code/s3_unarchive.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: s3.yml 3 | 4 | - name: ANSISTRANO | s3_unarchive | Set archived file 5 | set_fact: 6 | ansistrano_archived_file: "{{ ansistrano_release_path.stdout }}/{{ ansistrano_s3_object | basename }}" 7 | 8 | - include: unarchive.yml -------------------------------------------------------------------------------- /tasks/update-code/copy_unarchive.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: copy.yml 3 | 4 | - name: ANSISTRANO | copy_unarchive | Set archived file 5 | set_fact: 6 | ansistrano_archived_file: "{{ ansistrano_release_path.stdout }}/{{ ansistrano_deploy_from | basename }}" 7 | 8 | - include: unarchive.yml 9 | -------------------------------------------------------------------------------- /tasks/update-code/download_unarchive.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: download.yml 3 | 4 | - name: ANSISTRANO | download_unarchive | Set archived file 5 | set_fact: 6 | ansistrano_archived_file: "{{ ansistrano_release_path.stdout }}/{{ ansistrano_get_url | basename }}" 7 | 8 | - include: unarchive.yml 9 | -------------------------------------------------------------------------------- /example/my-playbook/hosts: -------------------------------------------------------------------------------- 1 | web01 ansible_user=vagrant ansible_port=2222 ansible_host=127.0.0.1 ansible_ssh_private_key_file=../../.vagrant/machines/web01/virtualbox/private_key 2 | web02 ansible_user=vagrant ansible_port=2200 ansible_host=127.0.0.1 ansible_ssh_private_key_file=../../.vagrant/machines/web02/virtualbox/private_key -------------------------------------------------------------------------------- /tasks/anon-stats.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Sends anonymous stats if the user is ok with it 3 | - name: ANSISTRANO | Send anonymous stats 4 | uri: 5 | url: http://ansistrano.com/deploy 6 | method: POST 7 | timeout: 5 8 | when: ansistrano_allow_anonymous_stats 9 | run_once: true 10 | ignore_errors: yes 11 | delegate_to: 127.0.0.1 12 | sudo: false 13 | -------------------------------------------------------------------------------- /tasks/update-code/copy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ANSISTRANO | SCP | Create release folder 3 | file: 4 | state: directory 5 | path: "{{ ansistrano_release_path.stdout }}" 6 | 7 | - name: ANSISTRANO | SCP | Deploy existing code to remote servers 8 | copy: 9 | src: "{{ ansistrano_deploy_from }}" 10 | dest: "{{ ansistrano_release_path.stdout }}" 11 | -------------------------------------------------------------------------------- /tasks/update-code/unarchive.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ANSISTRANO | Unarchive | Unarchive source 3 | unarchive: 4 | copy: no 5 | src: "{{ ansistrano_archived_file }}" 6 | dest: "{{ ansistrano_release_path.stdout }}" 7 | 8 | - name: ANSISTRANO | Unarchive | Delete archived file 9 | file: 10 | path: "{{ ansistrano_archived_file }}" 11 | state: absent -------------------------------------------------------------------------------- /tasks/update-code/download.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ANSISTRANO | download | Create release folder 3 | file: 4 | state: directory 5 | path: "{{ ansistrano_release_path.stdout }}" 6 | 7 | - name: ANSISTRANO | download | Download artifact 8 | get_url: 9 | url: "{{ ansistrano_get_url }}" 10 | dest: "{{ ansistrano_release_path.stdout }}/{{ ansistrano_get_url | basename }}" 11 | -------------------------------------------------------------------------------- /example/my-playbook/deploy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Deploy example app to my-server.com 3 | hosts: all 4 | vars: 5 | ansistrano_deploy_from: "{{ playbook_dir }}/../my-app" 6 | ansistrano_deploy_to: "/tmp/my-app.com" 7 | ansistrano_keep_releases: 3 8 | # There seems to be an issue with rsync in vagrant 9 | ansistrano_deploy_via: copy 10 | roles: 11 | - { role: carlosbuenosvinos.ansistrano-deploy } -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.define 'web01' do |config| 3 | config.vm.box = 'ubuntu/trusty64' 4 | config.vm.hostname = 'web01' 5 | config.vm.synced_folder '.', '/vagrant', disabled: true 6 | end 7 | 8 | config.vm.define 'web02' do |config| 9 | config.vm.box = 'ubuntu/trusty64' 10 | config.vm.hostname = 'web02' 11 | config.vm.synced_folder '.', '/vagrant', disabled: true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/tasks/create-internal-paths.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # We cannot create the symlinks unless these subfolders exist in the destination release folder 3 | - name: ANSISTRANO | Ensure internal path foo exists 4 | file: 5 | state: directory 6 | path: "{{ ansistrano_release_path.stdout }}/foo" 7 | 8 | - name: ANSISTRANO | Ensure internal path xxx/yyy exists 9 | file: 10 | state: directory 11 | path: "{{ ansistrano_release_path.stdout }}/xxx/yyy" 12 | 13 | - name: ANSISTRANO | Ensure internal path files exists 14 | file: 15 | state: directory 16 | path: "{{ ansistrano_release_path.stdout }}/files" 17 | -------------------------------------------------------------------------------- /tasks/update-code/s3.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ANSISTRANO | S3 | Create release folder 3 | file: 4 | state: directory 5 | path: "{{ ansistrano_release_path.stdout }}" 6 | 7 | - name: ANSISTRANO | S3 | Get object from S3 8 | s3: 9 | bucket: "{{ ansistrano_s3_bucket }}" 10 | object: "{{ ansistrano_s3_object }}" 11 | dest: "{{ ansistrano_release_path.stdout }}/{{ ansistrano_s3_object | basename }}" 12 | mode: get 13 | region: "{{ ansistrano_s3_region }}" 14 | aws_access_key: "{{ ansistrano_s3_aws_access_key | default(omit) }}" 15 | aws_secret_key: "{{ ansistrano_s3_aws_secret_key | default(omit) }}" 16 | -------------------------------------------------------------------------------- /tasks/symlink.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Migration check from rsync deployment 4 | - stat: 5 | path: "{{ ansistrano_deploy_to }}/{{ ansistrano_current_dir }}" 6 | register: stat_current_dir 7 | 8 | - name: ANSISTRANO | Remove current folder if it's a directory 9 | file: 10 | state: absent 11 | path: "{{ ansistrano_deploy_to }}/{{ ansistrano_current_dir }}" 12 | when: stat_current_dir.stat.isdir is defined and stat_current_dir.stat.isdir 13 | 14 | # Performs symlink exchange 15 | - name: ANSISTRANO | Change softlink to new release 16 | file: 17 | state: link 18 | path: "{{ ansistrano_deploy_to }}/{{ ansistrano_current_dir }}" 19 | src: "./{{ ansistrano_version_dir }}/{{ ansistrano_release_version }}" 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: required 3 | dist: trusty 4 | 5 | matrix: 6 | include: 7 | # Remove temporarily 1.8, perhaps forever 8 | # - env: ANSIBLE_VERSION=1.8.4 9 | - env: ANSIBLE_VERSION=1.9.5 10 | # We don´t test 2.0.x series as they are very buggy 11 | - env: ANSIBLE_VERSION=2.1.2 12 | - env: ANSIBLE_VERSION=2.2.0 13 | 14 | before_install: 15 | - sudo apt-get -y install software-properties-common 16 | - sudo apt-get -y install python-pip 17 | - sudo pip install ansible==$ANSIBLE_VERSION 18 | - ansible --version 19 | 20 | script: 21 | - echo localhost > inventory 22 | - ansible-playbook -i inventory --connection=local --sudo -v test/main.yml 23 | 24 | notifications: 25 | webhooks: https://galaxy.ansible.com/api/v1/notifications/ -------------------------------------------------------------------------------- /tasks/update-code.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Update code deployment step 3 | - name: ANSISTRANO | Get release version 4 | set_fact: 5 | ansistrano_release_version: "{{ lookup('pipe', 'date -u +%Y%m%d%H%M%SZ') }}" 6 | run_once: true 7 | when: ansistrano_release_version is not defined 8 | delegate_to: 127.0.0.1 9 | 10 | - name: ANSISTRANO | Get release path 11 | command: echo "{{ ansistrano_releases_path.stdout }}/{{ ansistrano_release_version }}" 12 | register: ansistrano_release_path 13 | 14 | - include: "update-code/{{ ansistrano_deploy_via | default('rsync') }}.yml" 15 | 16 | - name: ANSISTRANO | Copy release version into REVISION file 17 | copy: 18 | content: "{{ ansistrano_release_version }}" 19 | dest: "{{ ansistrano_release_path.stdout }}/REVISION" 20 | -------------------------------------------------------------------------------- /tasks/setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Setup folders 3 | - name: ANSISTRANO | Ensure deployment base path exists 4 | file: 5 | state: directory 6 | path: "{{ ansistrano_deploy_to }}" 7 | 8 | - name: ANSISTRANO | Set releases path 9 | command: echo "{{ ansistrano_deploy_to }}/{{ ansistrano_version_dir }}" 10 | register: ansistrano_releases_path 11 | 12 | - name: ANSISTRANO | Ensure releases folder exists 13 | file: 14 | state: directory 15 | path: "{{ ansistrano_releases_path.stdout }}" 16 | 17 | - name: ANSISTRANO | Set shared path 18 | command: echo "{{ ansistrano_deploy_to }}/shared" 19 | register: ansistrano_shared_path 20 | 21 | - name: ANSISTRANO | Ensure shared elements folder exists 22 | file: 23 | state: directory 24 | path: "{{ ansistrano_shared_path.stdout }}" 25 | -------------------------------------------------------------------------------- /tasks/update-code/rsync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ANSISTRANO | RSYNC | Get shared path (in rsync case) 3 | command: echo "{{ ansistrano_shared_path.stdout }}/.shared-copy" 4 | register: ansistrano_shared_rsync_copy_path 5 | 6 | - name: ANSISTRANO | RSYNC | Rsync application files to remote shared copy 7 | synchronize: 8 | src: "{{ ansistrano_deploy_from }}" 9 | dest: "{{ ansistrano_shared_rsync_copy_path.stdout }}" 10 | set_remote_user: "{{ ansistrano_rsync_set_remote_user }}" 11 | recursive: yes 12 | delete: yes 13 | archive: yes 14 | compress: yes 15 | rsync_opts: "{{ ansistrano_rsync_extra_params }}" 16 | 17 | - name: ANSISTRANO | RSYNC | Deploy existing code to servers 18 | command: cp -a {{ ansistrano_shared_rsync_copy_path.stdout }} {{ ansistrano_release_path.stdout }} 19 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: ansistrano 4 | description: Ansible role to deploy scripting applications like PHP, Python, Ruby, etc. in a Capistrano style 5 | company: Ansistrano 6 | license: MIT 7 | min_ansible_version: 1.8 8 | platforms: 9 | - name: EL 10 | versions: 11 | - all 12 | - name: GenericUNIX 13 | versions: 14 | - all 15 | - name: Fedora 16 | versions: 17 | - all 18 | - name: opensuse 19 | versions: 20 | - all 21 | - name: Amazon 22 | versions: 23 | - all 24 | - name: GenericBSD 25 | versions: 26 | - all 27 | - name: FreeBSD 28 | versions: 29 | - all 30 | - name: Ubuntu 31 | versions: 32 | - all 33 | - name: SLES 34 | versions: 35 | - all 36 | - name: GenericLinux 37 | versions: 38 | - all 39 | - name: Debian 40 | versions: 41 | - all 42 | categories: 43 | - cloud 44 | - web 45 | dependencies: [] 46 | -------------------------------------------------------------------------------- /test/tasks/create-shared-paths.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ANSISTRANO | Ensure shared path 1 exists 3 | file: 4 | state: directory 5 | path: "{{ ansistrano_deploy_to }}/shared/blah" 6 | 7 | - name: ANSISTRANO | Ensure shared path 2 exists 8 | file: 9 | state: directory 10 | path: "{{ ansistrano_deploy_to }}/shared/foo/bar" 11 | 12 | - name: ANSISTRANO | Ensure shared path 3 exists 13 | file: 14 | state: directory 15 | path: "{{ ansistrano_deploy_to }}/shared/xxx/yyy/zzz" 16 | 17 | - name: ANSISTRANO | Ensure shared file 1 exists 18 | file: 19 | state: touch 20 | path: "{{ ansistrano_deploy_to }}/shared/test.txt" 21 | 22 | - name: ANSISTRANO | Ensure shared files folder exists 23 | file: 24 | state: directory 25 | path: "{{ ansistrano_deploy_to }}/shared/files" 26 | 27 | - name: ANSISTRANO | Ensure shared file 2 exists 28 | file: 29 | state: touch 30 | path: "{{ ansistrano_deploy_to }}/shared/files/test.txt" 31 | -------------------------------------------------------------------------------- /tasks/update-code/svn.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ANSISTRANO | SVN | Update remote repository 3 | subversion: 4 | repo: "{{ ansistrano_svn_repo }}/{{ ansistrano_svn_branch }}" 5 | dest: "{{ ansistrano_deploy_to }}/repo" 6 | revision: "{{ ansistrano_svn_revision }}" 7 | username: "{{ ansistrano_svn_username }}" 8 | password: "{{ ansistrano_svn_password }}" 9 | force: yes 10 | register: ansistrano_svn_result_update 11 | 12 | - name: ANSISTRANO | SVN | Register ansistrano_svn_result variable 13 | set_fact: ansistrano_svn_result={{ ansistrano_svn_result_update }} 14 | 15 | - name: ANSISTRANO | SVN | Create release folder 16 | file: 17 | state: directory 18 | path: "{{ ansistrano_release_path.stdout }}" 19 | 20 | - name: ANSISTRANO | SVN | Copy repo to release path 21 | subversion: 22 | repo: "{{ ansistrano_deploy_to }}/repo" 23 | dest: "{{ ansistrano_release_path.stdout }}" 24 | revision: "{{ ansistrano_svn_revision }}" 25 | username: "{{ ansistrano_svn_username }}" 26 | password: "{{ ansistrano_svn_password }}" 27 | export: yes 28 | force: yes 29 | register: ansistrano_svn_result_export 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 Carlos Buenosvinos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | 3 | ### Vagrant template 4 | .vagrant/ 5 | 6 | ### JetBrains template 7 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 8 | 9 | *.iml 10 | 11 | ## Directory-based project format: 12 | .idea/ 13 | # if you remove the above rule, at least ignore the following: 14 | 15 | # User-specific stuff: 16 | # .idea/workspace.xml 17 | # .idea/tasks.xml 18 | # .idea/dictionaries 19 | 20 | # Sensitive or high-churn files: 21 | # .idea/dataSources.ids 22 | # .idea/dataSources.xml 23 | # .idea/sqlDataSources.xml 24 | # .idea/dynamic.xml 25 | # .idea/uiDesigner.xml 26 | 27 | # Gradle: 28 | # .idea/gradle.xml 29 | # .idea/libraries 30 | 31 | # Mongo Explorer plugin: 32 | # .idea/mongoSettings.xml 33 | 34 | ## File-based project format: 35 | *.ipr 36 | *.iws 37 | 38 | ## Plugin-specific files: 39 | 40 | # IntelliJ 41 | /out/ 42 | 43 | # mpeltonen/sbt-idea plugin 44 | .idea_modules/ 45 | 46 | # JIRA plugin 47 | atlassian-ide-plugin.xml 48 | 49 | # Crashlytics plugin (for Android Studio and IntelliJ) 50 | com_crashlytics_export_strings.xml 51 | crashlytics.properties 52 | crashlytics-build.properties 53 | 54 | -------------------------------------------------------------------------------- /tasks/rsync-deploy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Migration check from symlink deployment 4 | - stat: 5 | path: "{{ ansistrano_deploy_to }}/{{ ansistrano_current_dir }}" 6 | register: stat_current_dir 7 | 8 | - name: ANSISTRANO | Remove current folder if it's a symlink 9 | file: 10 | state: absent 11 | path: "{{ ansistrano_deploy_to }}/{{ ansistrano_current_dir }}" 12 | when: stat_current_dir.stat.islnk is defined and stat_current_dir.stat.islnk 13 | 14 | # Perform rsync deployment 15 | - name: ANSISTRANO | Ensure current folder is a directory 16 | file: 17 | state: directory 18 | path: "{{ ansistrano_deploy_to }}/{{ ansistrano_current_dir }}" 19 | 20 | - name: ANSISTRANO | Sync release to new current path 21 | command: rsync -a -F --no-times --delete-after "{{ ansistrano_release_path.stdout }}/" "{{ ansistrano_deploy_to }}/{{ ansistrano_current_dir }}/" 22 | 23 | # Ensure symlinks target paths is absent 24 | - name: ANSISTRANO | Ensure shared paths targets are absent 25 | file: 26 | state: absent 27 | path: "{{ ansistrano_deploy_to }}/{{ ansistrano_current_dir }}/{{ item }}" 28 | with_items: "{{ ansistrano_shared_paths }}" 29 | 30 | # Symlinks shared paths 31 | - name: ANSISTRANO | Create softlinks for shared paths 32 | file: 33 | state: link 34 | path: "{{ ansistrano_deploy_to }}/{{ ansistrano_current_dir }}/{{ item }}" 35 | src: "{{ item | regex_replace('[^\\/]*', '..') }}/shared/{{ item }}" 36 | with_items: "{{ ansistrano_shared_paths }}" 37 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ANSISTRANO | Check BC for early adopters 3 | fail: msg="ansistrano_custom_tasks_path is not used anymore. Please, check the documentation at https://github.com/ansistrano/deploy" 4 | when: ansistrano_custom_tasks_path is defined 5 | 6 | - include: "{{ ansistrano_before_setup_tasks_file | default('empty.yml') }}" 7 | 8 | - include: setup.yml 9 | 10 | - include: "{{ ansistrano_after_setup_tasks_file | default('empty.yml') }}" 11 | 12 | - include: "{{ ansistrano_before_update_code_tasks_file | default('empty.yml') }}" 13 | 14 | - include: update-code.yml 15 | 16 | - include: "{{ ansistrano_after_update_code_tasks_file | default('empty.yml') }}" 17 | 18 | - include: "{{ ansistrano_before_symlink_shared_tasks_file | default('empty.yml') }}" 19 | 20 | - include: symlink-shared.yml 21 | 22 | - include: "{{ ansistrano_after_symlink_shared_tasks_file | default('empty.yml') }}" 23 | 24 | - include: "{{ ansistrano_before_symlink_tasks_file | default('empty.yml') }}" 25 | 26 | - include: symlink.yml 27 | when: ansistrano_current_via == "symlink" 28 | 29 | - include: rsync-deploy.yml 30 | when: ansistrano_current_via == "rsync" 31 | 32 | - include: "{{ ansistrano_after_symlink_tasks_file | default('empty.yml') }}" 33 | 34 | - include: "{{ ansistrano_before_cleanup_tasks_file | default('empty.yml') }}" 35 | 36 | - include: cleanup.yml 37 | 38 | - include: "{{ ansistrano_after_cleanup_tasks_file | default('empty.yml') }}" 39 | 40 | - include: anon-stats.yml 41 | -------------------------------------------------------------------------------- /test/svn.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Tests for svn strategy 3 | - name: Given no previous deploy with SVN 4 | hosts: all 5 | vars: 6 | ansistrano_deploy_to: "/tmp/svn/my-app.com" 7 | tasks: 8 | - name: Assert ansistrano_deploy_to path does not exist 9 | stat: 10 | path: "{{ ansistrano_deploy_to }}" 11 | register: st 12 | - debug: 13 | msg: "Path does not exist and is a directory" 14 | when: st.stat.exists is defined and not st.stat.exists 15 | 16 | - name: When deploying using SVN 17 | hosts: all 18 | vars: 19 | ansistrano_deploy_via: "svn" 20 | ansistrano_svn_repo: https://github.com/ansistrano/deploy.git 21 | ansistrano_deploy_to: "/tmp/svn/my-app.com" 22 | roles: 23 | - { role: local-ansistrano } 24 | 25 | - name: Then a successful deploy with svn should be done 26 | hosts: all 27 | vars: 28 | ansistrano_deploy_to: "/tmp/svn/my-app.com" 29 | tasks: 30 | - name: Assert ansistrano_deploy_to path does exist 31 | stat: 32 | path: "{{ ansistrano_deploy_to }}" 33 | register: st 34 | - debug: 35 | msg: "Path exists and is a directory" 36 | when: st.stat.exists is defined and st.stat.exists 37 | 38 | - name: And I should be able to do a second deploy 39 | hosts: all 40 | vars: 41 | ansistrano_deploy_via: "svn" 42 | ansistrano_svn_repo: https://github.com/ansistrano/deploy.git 43 | ansistrano_deploy_to: "/tmp/svn/my-app.com" 44 | roles: 45 | - { role: local-ansistrano } 46 | -------------------------------------------------------------------------------- /test/download.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Tests for download strategy 3 | - name: Given no previous deploy with download strategy 4 | hosts: all 5 | vars: 6 | ansistrano_deploy_to: "/tmp/download/my-app.com" 7 | tasks: 8 | - name: Assert ansistrano_deploy_to path does not exist 9 | stat: 10 | path: "{{ ansistrano_deploy_to }}" 11 | register: st 12 | - debug: 13 | msg: "Path does not exist and is a directory" 14 | when: st.stat.exists is defined and not st.stat.exists 15 | 16 | - name: When deploying using download strategy 17 | hosts: all 18 | vars: 19 | ansistrano_deploy_via: "download_unarchive" 20 | ansistrano_get_url: https://github.com/ansistrano/deploy/archive/1.4.1.zip 21 | ansistrano_deploy_to: "/tmp/download/my-app.com" 22 | roles: 23 | - { role: local-ansistrano } 24 | 25 | - name: Then a successful deploy with download strategy should be done 26 | hosts: all 27 | vars: 28 | ansistrano_deploy_to: "/tmp/download/my-app.com" 29 | tasks: 30 | - name: Assert ansistrano_deploy_to path does exist 31 | stat: 32 | path: "{{ ansistrano_deploy_to }}" 33 | register: st 34 | - debug: 35 | msg: "Path exists and is a directory" 36 | when: st.stat.exists is defined and st.stat.exists 37 | 38 | - name: And I should be able to do a second deploy 39 | hosts: all 40 | vars: 41 | ansistrano_deploy_via: "download_unarchive" 42 | ansistrano_get_url: https://github.com/ansistrano/deploy/archive/1.4.1.zip 43 | ansistrano_deploy_to: "/tmp/download/my-app.com" 44 | roles: 45 | - { role: local-ansistrano } 46 | -------------------------------------------------------------------------------- /tasks/symlink-shared.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Ensure symlinks target paths is absent 3 | # This was removed in 1.7.3 to improve speed but it introduced regressions in cases where 4 | # there are .gitkeep files in such folders (common practice in some PHP frameworks) 5 | - name: ANSISTRANO | Ensure shared paths targets are absent 6 | file: 7 | state: absent 8 | path: "{{ ansistrano_release_path.stdout }}/{{ item }}" 9 | with_flattened: 10 | - "{{ ansistrano_shared_paths }}" 11 | - "{{ ansistrano_shared_files }}" 12 | 13 | # Ensure shared path exists 14 | - name: ANSISTRANO | Ensure shared paths exists 15 | file: 16 | state: directory 17 | path: "{{ ansistrano_deploy_to }}/shared/{{ item }}" 18 | with_items: "{{ ansistrano_shared_paths }}" 19 | 20 | # Ensure basedir shared files exists 21 | - name: ANSISTRANO | Ensure basedir shared files exists 22 | file: 23 | state: directory 24 | path: "{{ ansistrano_deploy_to }}/shared/{{ item | dirname }}" 25 | with_items: "{{ ansistrano_shared_files }}" 26 | 27 | # Symlinks shared paths and files 28 | - name: ANSISTRANO | Create softlinks for shared paths and files 29 | file: 30 | state: link 31 | path: "{{ ansistrano_release_path.stdout }}/{{ item }}" 32 | src: "{{ item | regex_replace('[^\\/]*', '..') }}/../shared/{{ item }}" 33 | with_flattened: 34 | - "{{ ansistrano_shared_paths }}" 35 | - "{{ ansistrano_shared_files }}" 36 | 37 | # Remove previous .rsync-filter file (rsync current deployment) 38 | - name: ANSISTRANO | Ensure .rsync-filter is absent 39 | file: 40 | state: absent 41 | path: "{{ ansistrano_release_path.stdout }}/.rsync-filter" 42 | when: ansistrano_current_via == "rsync" 43 | 44 | # Setup .rsync-filter file for current rsync deployment (exclude shared folders for rsync current deployment) 45 | - name: ANSISTRANO | Setup .rsync-filter with shared-folders 46 | lineinfile: 47 | dest: "{{ ansistrano_release_path.stdout }}/.rsync-filter" 48 | line: "- /{{ item }}" 49 | create: yes 50 | with_items: "{{ ansistrano_shared_paths }}" 51 | when: ansistrano_current_via == "rsync" 52 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Path where the code to deploy is 3 | ansistrano_deploy_from: "{{ playbook_dir }}" 4 | 5 | # Path where the code must be deployed to 6 | ansistrano_deploy_to: "/var/www/my-app" 7 | 8 | # Folder name for the releases 9 | ansistrano_version_dir: "releases" 10 | 11 | # Softlink name for the current release 12 | ansistrano_current_dir: "current" 13 | 14 | # Current directory deployment strategy 15 | ansistrano_current_via: "symlink" 16 | 17 | # Shared paths to symlink to release dir 18 | ansistrano_shared_paths: [] 19 | 20 | # Shared files to symlink to release dir 21 | ansistrano_shared_files: [] 22 | 23 | # Number of releases to keep in your hosts, if 0, unlimited releases will be kept 24 | ansistrano_keep_releases: 0 25 | 26 | # Deployment strategies variables 27 | 28 | # Due to runtime variable evaluation, the ansistrano_deploy_via default is actually 29 | # defined in update-code.yml instead of this file. You can still override it in your 30 | # playbook as needed. 31 | # ansistrano_deploy_via: "rsync" 32 | 33 | ## GIT pull strategy 34 | ansistrano_git_repo: git@github.com:USERNAME/REPO.git 35 | ansistrano_git_branch: master 36 | ansistrano_git_repo_tree: "" 37 | ansistrano_git_identity_key_path: "" 38 | 39 | ## SVN pull strategy 40 | ansistrano_svn_repo: "https://svn.company.com/project" 41 | ansistrano_svn_branch: "trunk" 42 | ansistrano_svn_revision: "HEAD" 43 | ansistrano_svn_username: "user" 44 | ansistrano_svn_password: "Pa$$word" 45 | 46 | ## RSYNC push strategy 47 | ansistrano_rsync_extra_params: "" 48 | ## put user@ for the remote paths. If you have a custom ssh config to define 49 | ## the remote user for a host that does not match the inventory user, 50 | ## you should set this parameter to "no". 51 | ansistrano_rsync_set_remote_user: yes 52 | 53 | ## Download strategy 54 | ansistrano_get_url: https://github.com/someproject/somearchive.tar.gz 55 | 56 | ## S3 get strategy 57 | ansistrano_s3_bucket: s3bucket 58 | ansistrano_s3_object: s3object.tgz 59 | ansistrano_s3_region: eu-west-1 60 | 61 | ## Sends anonymous stats to the www.ansistrano.com servers 62 | ## You can disallow it by just setting this parameter to "no" in your playbook 63 | ansistrano_allow_anonymous_stats: yes 64 | -------------------------------------------------------------------------------- /tasks/update-code/git.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ANSISTRANO | GIT | Ensure GIT deployment key is up to date 3 | template: 4 | src: "{{ ansistrano_git_identity_key_path }}" 5 | dest: "{{ ansistrano_deploy_to }}/git_identity_key" 6 | mode: 0400 7 | when: ansistrano_git_identity_key_path|trim != "" 8 | 9 | - name: ANSISTRANO | GIT | Update remote repository 10 | git: 11 | repo: "{{ ansistrano_git_repo }}" 12 | dest: "{{ ansistrano_deploy_to }}/repo" 13 | version: "{{ ansistrano_git_branch }}" 14 | accept_hostkey: true 15 | update: yes 16 | force: yes 17 | register: ansistrano_git_result_update 18 | when: ansistrano_git_identity_key_path|trim == '' 19 | 20 | - name: ANSISTRANO | GIT | Update remote repository using SSH key 21 | git: 22 | repo: "{{ ansistrano_git_repo }}" 23 | dest: "{{ ansistrano_deploy_to }}/repo" 24 | version: "{{ ansistrano_git_branch }}" 25 | accept_hostkey: true 26 | update: yes 27 | force: yes 28 | key_file: "{{ ansistrano_deploy_to }}/git_identity_key" 29 | register: ansistrano_git_result_update_ssh 30 | when: ansistrano_git_identity_key_path|trim != "" 31 | 32 | - name: ANSISTRANO | GIT | Register ansistrano_git_result variable 33 | set_fact: ansistrano_git_result={{ ansistrano_git_result_update_ssh if ansistrano_git_result_update|skipped else ansistrano_git_result_update }} 34 | 35 | - name: ANSISTRANO | GIT | Shred GIT deployment key 36 | command: shred -f "{{ ansistrano_deploy_to }}/git_identity_key" 37 | when: ansistrano_git_identity_key_path|trim != "" 38 | 39 | - name: ANSISTRANO | GIT | Set git_real_repo_tree 40 | set_fact: 41 | ansistrano_git_real_repo_tree: "{{ ansistrano_git_repo_tree | trim | regex_replace('^[/]*', '') | regex_replace('[/]*$', '') }}" 42 | 43 | - name: ANSISTRANO | GIT | Create release folder 44 | file: 45 | state: directory 46 | path: "{{ ansistrano_release_path.stdout }}" 47 | 48 | - name: ANSISTRANO | GIT | Archive and unarchive repo to release path 49 | shell: git archive --format=tar {{ ansistrano_git_branch }} | tar -x -f - -C "{{ ansistrano_release_path.stdout }}" 50 | args: 51 | chdir: "{{ ansistrano_deploy_to }}/repo" 52 | when: ansistrano_git_real_repo_tree == "" 53 | 54 | - name: ANSISTRANO | GIT | Archive and unarchive subtree["{{ ansistrano_git_real_repo_tree }}"] of repo to release path 55 | shell: git archive --format=tar {{ ansistrano_git_branch }} "{{ ansistrano_git_real_repo_tree }}" | tar -x --strip-components "{{ ansistrano_git_real_repo_tree.split('/') | length }}" -f - -C "{{ ansistrano_release_path.stdout }}" 56 | args: 57 | chdir: "{{ ansistrano_deploy_to }}/repo" 58 | when: ansistrano_git_real_repo_tree != "" 59 | -------------------------------------------------------------------------------- /test/git.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Tests for git strategy 3 | - name: Given no previous deploy with Git 4 | hosts: all 5 | vars: 6 | ansistrano_deploy_to: "/tmp/git/my-app.com" 7 | tasks: 8 | - name: Assert ansistrano_deploy_to path does not exist 9 | stat: 10 | path: "{{ ansistrano_deploy_to }}" 11 | register: st 12 | - debug: 13 | msg: "Path does not exist and is a directory" 14 | when: st.stat.exists is defined and not st.stat.exists 15 | 16 | - name: When deploying using Git 17 | hosts: all 18 | vars: 19 | ansistrano_deploy_via: "git" 20 | ansistrano_git_repo: https://github.com/ansistrano/deploy.git 21 | ansistrano_git_branch: master 22 | ansistrano_deploy_to: "/tmp/git/my-app.com" 23 | roles: 24 | - { role: local-ansistrano } 25 | 26 | - name: Then a successful deploy with git should be done 27 | hosts: all 28 | vars: 29 | ansistrano_deploy_to: "/tmp/git/my-app.com" 30 | tasks: 31 | - name: Assert ansistrano_deploy_to path does exist 32 | stat: 33 | path: "{{ ansistrano_deploy_to }}" 34 | register: st 35 | - debug: 36 | msg: "Path exists and is a directory" 37 | when: st.stat.exists is defined and st.stat.exists 38 | 39 | - name: And I should be able to do a second deploy 40 | hosts: all 41 | vars: 42 | ansistrano_deploy_via: "git" 43 | ansistrano_git_repo: https://github.com/ansistrano/deploy.git 44 | ansistrano_git_branch: master 45 | ansistrano_deploy_to: "/tmp/git/my-app.com" 46 | roles: 47 | - { role: local-ansistrano } 48 | 49 | - name: When deploying using Git (repo_tree with Single-Layer folder) 50 | hosts: all 51 | vars: 52 | ansistrano_deploy_via: "git" 53 | ansistrano_git_repo: https://github.com/ansistrano/deploy.git 54 | ansistrano_git_branch: master 55 | ansistrano_git_repo_tree: test 56 | ansistrano_deploy_to: "/tmp/git/my-app.com" 57 | pre_tasks: 58 | - name: Clear ansistrano_deploy_to 59 | file: 60 | path: "{{ ansistrano_deploy_to }}" 61 | state: absent 62 | roles: 63 | - { role: local-ansistrano } 64 | tasks: 65 | - name: Assert ansistrano_deploy_to/current/main.yml file does exist 66 | stat: 67 | path: "{{ ansistrano_deploy_to }}/current/main.yml" 68 | register: st 69 | - fail: 70 | msg: "File not exists" 71 | when: st.stat.exists is not defined or st.stat.exists == False or st.stat.isdir == True 72 | 73 | - name: When deploying using Git (repo_tree with Multi-Layer folder) 74 | hosts: all 75 | vars: 76 | ansistrano_deploy_via: "git" 77 | ansistrano_git_repo: https://github.com/ansistrano/deploy.git 78 | ansistrano_git_branch: master 79 | ansistrano_git_repo_tree: test/roles/ 80 | ansistrano_deploy_to: "/tmp/git/my-app.com" 81 | pre_tasks: 82 | - name: Clear ansistrano_deploy_to 83 | file: 84 | path: "{{ ansistrano_deploy_to }}" 85 | state: absent 86 | roles: 87 | - { role: local-ansistrano } 88 | tasks: 89 | - name: Assert ansistrano_deploy_to/current/local-ansistrano symbolic link does exist 90 | stat: 91 | path: "{{ ansistrano_deploy_to }}/current/local-ansistrano" 92 | register: st 93 | - fail: 94 | msg: "Symbolic link not exists" 95 | when: st.stat.exists is not defined or st.stat.exists == False or st.stat.islnk == False 96 | -------------------------------------------------------------------------------- /test/rsync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Tests for default rsync strategy 3 | - name: Given no previous deploy 4 | hosts: all 5 | vars: 6 | ansistrano_deploy_to: "/tmp/my-app.com" 7 | tasks: 8 | - name: Assert ansistrano_deploy_to path does not exist 9 | stat: 10 | path: "{{ ansistrano_deploy_to }}" 11 | register: st 12 | - debug: 13 | msg: "Path does not exist and is a directory" 14 | when: st.stat.exists is defined and not st.stat.exists 15 | 16 | - name: When deploying 17 | hosts: all 18 | vars: 19 | ansistrano_deploy_to: "/tmp/my-app.com" 20 | ansistrano_after_setup_tasks_file: "{{ playbook_dir }}/tasks/create-shared-paths.yml" 21 | ansistrano_after_update_code_tasks_file: "{{ playbook_dir }}/tasks/create-internal-paths.yml" 22 | ansistrano_shared_paths: 23 | - blah 24 | - foo/bar 25 | - xxx/yyy/zzz 26 | ansistrano_shared_files: 27 | - test.txt 28 | - files/test.txt 29 | roles: 30 | - { role: local-ansistrano } 31 | 32 | - name: Then a successful deploy with rsync should be done 33 | hosts: all 34 | vars: 35 | ansistrano_deploy_to: "/tmp/my-app.com" 36 | tasks: 37 | - name: Assert ansistrano_deploy_to path does exist 38 | stat: 39 | path: "{{ ansistrano_deploy_to }}" 40 | register: st 41 | - debug: 42 | msg: "Path exists and is a directory" 43 | when: st.stat.exists is defined and st.stat.exists 44 | - name: Assert ansistrano_deploy_to/current path does exist 45 | stat: 46 | path: "{{ ansistrano_deploy_to }}/current" 47 | register: st 48 | - fail: 49 | msg: "Path is not a symlink" 50 | when: st.stat.exists is defined and st.stat.exists and st.stat.islnk == False 51 | - debug: 52 | msg: "Path exists and is a symlink" 53 | when: st.stat.exists is defined and st.stat.exists and st.stat.islnk 54 | 55 | - name: And I should be able to do a second deploy 56 | hosts: all 57 | vars: 58 | ansistrano_deploy_to: "/tmp/my-app.com" 59 | ansistrano_after_setup_tasks_file: "{{ playbook_dir }}/tasks/create-shared-paths.yml" 60 | ansistrano_after_update_code_tasks_file: "{{ playbook_dir }}/tasks/create-internal-paths.yml" 61 | ansistrano_shared_paths: 62 | - blah 63 | - foo/bar 64 | - xxx/yyy/zzz 65 | ansistrano_shared_files: 66 | - test.txt 67 | - files/test.txt 68 | roles: 69 | - { role: local-ansistrano } 70 | 71 | # Tests for rsync strategy with "current" via rsync (instead of the default symlink) 72 | - name: When deploying with rsync current strategy 73 | hosts: all 74 | vars: 75 | ansistrano_deploy_to: "/tmp/my-app.com" 76 | ansistrano_after_setup_tasks_file: "{{ playbook_dir }}/tasks/create-shared-paths.yml" 77 | ansistrano_after_update_code_tasks_file: "{{ playbook_dir }}/tasks/create-internal-paths.yml" 78 | ansistrano_current_via: "rsync" 79 | ansistrano_shared_paths: 80 | - blah 81 | - foo/bar 82 | - xxx/yyy/zzz 83 | ansistrano_shared_files: 84 | - test.txt 85 | - files/test.txt 86 | roles: 87 | - { role: local-ansistrano } 88 | 89 | - name: Then a successful deploy with rsync should be done 90 | hosts: all 91 | vars: 92 | ansistrano_deploy_to: "/tmp/my-app.com" 93 | tasks: 94 | - name: Assert ansistrano_deploy_to path does exist 95 | stat: 96 | path: "{{ ansistrano_deploy_to }}" 97 | register: st 98 | - debug: 99 | msg: "Path exists and is a directory" 100 | when: st.stat.exists is defined and st.stat.exists 101 | - name: Assert ansistrano_deploy_to/current path does exist 102 | stat: 103 | path: "{{ ansistrano_deploy_to }}/current" 104 | register: st 105 | - fail: 106 | msg: "Path is not a directory" 107 | when: st.stat.exists is defined and st.stat.exists and st.stat.isdir == False 108 | - debug: 109 | msg: "Path exists and is a directory" 110 | when: st.stat.exists is defined and st.stat.exists and st.stat.isdir 111 | 112 | - name: And I should be able to do a second deploy 113 | hosts: all 114 | vars: 115 | ansistrano_deploy_to: "/tmp/my-app.com" 116 | ansistrano_after_setup_tasks_file: "{{ playbook_dir }}/tasks/create-shared-paths.yml" 117 | ansistrano_after_update_code_tasks_file: "{{ playbook_dir }}/tasks/create-internal-paths.yml" 118 | ansistrano_current_via: "rsync" 119 | ansistrano_shared_paths: 120 | - blah 121 | - foo/bar 122 | - xxx/yyy/zzz 123 | ansistrano_shared_files: 124 | - test.txt 125 | - files/test.txt 126 | roles: 127 | - { role: local-ansistrano } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ansistrano 2 | ========== 3 | 4 | [![Build Status](https://travis-ci.org/ansistrano/deploy.svg?branch=master)](https://travis-ci.org/ansistrano/deploy) 5 | 6 | **ansistrano.deploy** and **ansistrano.rollback** are Ansible roles to easily manage the deployment process for scripting applications such as PHP, Python and Ruby. It's an Ansible port for Capistrano. 7 | 8 | History 9 | ------- 10 | 11 | [Capistrano](http://capistranorb.com/) is a remote server automation tool and it's currently in Version 3. [Version 2.0](https://github.com/capistrano/capistrano/tree/legacy-v2) was originally thought in order to deploy RoR applications. With additional plugins, you were able to deploy non Rails applications such as PHP and Python, with different deployment strategies, stages and much more. I loved Capistrano v2. I have used it a lot. I developed a plugin for it. 12 | 13 | Capistrano 2 was a great tool and it still works really well. However, it is not maintained anymore since the original team is working in v3. This new version does not have the same set of features so it is less powerful and flexible. Besides that, other new tools are becoming easier to use in order to deploy applications, such as Ansible. 14 | 15 | So, I have decided to stop using Capistrano because v2 is not maintained, v3 does not have enough features, and I can do everything Capistrano was doing with Ansible. If you are looking for alternatives, check Fabric or Chef Solo. 16 | 17 | Project name 18 | ------------ 19 | 20 | Ansistrano comes from Ansible + Capistrano, easy, isn't it? 21 | 22 | Early adopters 23 | -------------- 24 | 25 | If you were an early adopter, you should know we have broken BC by moving from using `ansistrano_custom_tasks_path` to individual and specific files per step. See "Role Variables". **The role displays a warning if the variable is defined and although your old playbooks may still run with no errors, you will see that your code is uploaded but custom tasks are not run.** 26 | 27 | Ansistrano anonymous usage stats 28 | -------------------------------- 29 | 30 | We have recently added an extra optional step in Ansistrano so that we can know how many people are deploying their applications with our project. Unfortunately, Ansible Galaxy does not provide any numbers on usage or downloads so this is one of the only ways we have to measure how many users we really have. 31 | 32 | You can check the code we use to store your anonyomus stats at [the ansistrano.com repo](https://github.com/ansistrano/ansistrano.com) and anyway, if you are not comfortable with this, you will always be able to disable this extra step by setting `ansistrano_allow_anonymous_stats` to false in your playbooks. 33 | 34 | Who is using Ansistrano? 35 | ------------------------ 36 | 37 | Is Ansistrano ready to be used? Here are some companies currently using it: 38 | 39 | * [Atrápalo](http://www.atrapalo.com) 40 | * [Another Place Productions](http://www.anotherplaceproductions.com) 41 | * [Suntransfers](http://www.suntransfers.com) 42 | * [Ulabox](https://www.ulabox.com) 43 | * [Euromillions.com](http://euromillions.com/) 44 | * [Uvinum](http://www.uvinum.com) 45 | * [Cycloid](http://www.cycloid.io) 46 | * [Spotahome](https://www.spotahome.com) 47 | * [Ofertix](http://www.ofertix.com) 48 | * [Nice&Crazy](http://www.niceandcrazy.com) 49 | * [Gstock](http://www.g-stock.es) 50 | * [CMP Group](http://www.teamcmp.com) 51 | * [Jolicode](http://jolicode.com/) 52 | * [Wavecontrol](http://monitoring.wavecontrol.com/ca/public/demo/) 53 | * [EnAlquiler](http://www.enalquiler.com/) 54 | * [ABA English](http://www.abaenglish.com/) 55 | * [Oferplan](http://oferplan.com/) 56 | * [Yubl](https://yubl.me/) 57 | * [Fluxus](http://www.fluxus.io/) 58 | * TechPump 59 | * [Nodo Ámbar](http://www.nodoambar.com/) 60 | * [Cabissimo](https://www.cabissimo.com) 61 | * [UNICEF Comité Español](https://www.unicef.es) 62 | 63 | If you are also using it, please let us know via a PR to this document. 64 | 65 | Requirements 66 | ------------ 67 | 68 | In order to deploy your apps with Ansistrano, you will need: 69 | 70 | * Ansible in your deployer machine 71 | 72 | Installation 73 | ------------ 74 | 75 | Ansistrano is an Ansible role distributed globally using [Ansible Galaxy](https://galaxy.ansible.com/). In order to install Ansistrano role you can use the following command. 76 | 77 | ``` 78 | $ ansible-galaxy install carlosbuenosvinos.ansistrano-deploy carlosbuenosvinos.ansistrano-rollback 79 | ``` 80 | 81 | Update 82 | ------ 83 | 84 | If you want to update the role, you need to pass **--force** parameter when installing. Please, check the following command: 85 | 86 | ``` 87 | $ ansible-galaxy install --force carlosbuenosvinos.ansistrano-deploy carlosbuenosvinos.ansistrano-rollback 88 | ``` 89 | 90 | Features 91 | -------- 92 | 93 | * Rollback in seconds (with ansistrano.rollback role) 94 | * Customize your deployment with hooks before and after critical steps 95 | * Save disk space keeping a maximum fixed releases in your hosts 96 | * Choose between SCP, RSYNC, GIT, SVN, HTTP Download or S3 GET deployment strategies (optional unarchive step included) 97 | 98 | Main workflow 99 | ------------- 100 | 101 | Ansistrano deploys applications following the Capistrano flow. 102 | 103 | * Setup phase: Creates the folder structure to hold your releases 104 | * Code update phase: Puts the new release into your hosts 105 | * Symlink phase: After deploying the new release into your hosts, this step changes the `current` softlink to new the release 106 | * Cleanup phase: Removes any old version based in the `ansistrano_keep_releases` parameter (see "Role Variables") 107 | 108 | ![Ansistrano Flow](https://raw.githubusercontent.com/ansistrano/deploy/master/docs/ansistrano-flow.png) 109 | 110 | Role Variables 111 | -------------- 112 | 113 | ```yaml 114 | - vars: 115 | ansistrano_deploy_from: "{{ playbook_dir }}" # Where my local project is (relative or absolute path) 116 | ansistrano_deploy_to: "/var/www/my-app" # Base path to deploy to. 117 | ansistrano_version_dir: "releases" # Releases folder name 118 | ansistrano_current_dir: "current" # Softlink name. You should rarely changed it. 119 | ansistrano_current_via: "symlink" # Deployment strategy who code should be deployed to current path. Options are symlink or rsync 120 | ansistrano_shared_paths: [] # Shared paths to symlink to release dir 121 | ansistrano_shared_files: [] # Shared files to symlink to release dir 122 | ansistrano_keep_releases: 0 # Releases to keep after a new deployment. See "Pruning old releases". 123 | ansistrano_deploy_via: "rsync" # Method used to deliver the code to the server. Options are copy, rsync, git, svn, s3 or download. Copy, download and s3 have an optional step to unarchive the downloaded file which can be used by adding _unarchive. You can check all the options inside tasks/update-code folder! 124 | ansistrano_allow_anonymous_stats: yes 125 | 126 | # Variables used in the rsync deployment strategy 127 | ansistrano_rsync_extra_params: "" # Extra parameters to use when deploying with rsync in a single string. Although Ansible allows an array this can cause problems if we try to add multiple --include args as it was reported in https://github.com/ansistrano/deploy/commit/e98942dc969d4e620313f00f003a7ea2eab67e86 128 | ansistrano_rsync_set_remote_user: yes # See [ansible synchronize module](http://docs.ansible.com/ansible/synchronize_module.html). Options are yes, no. 129 | 130 | # Variables used in the Git deployment strategy 131 | ansistrano_git_repo: git@github.com:USERNAME/REPO.git # Location of the git repository 132 | ansistrano_git_branch: master # What version of the repository to check out. This can be the full 40-character SHA-1 hash, the literal string HEAD, a branch name, or a tag name 133 | ansistrano_git_repo_tree: "" # If specified the subtree of the repository to deploy 134 | ansistrano_git_identity_key_path: "" # If specified this file is copied over and used as the identity key for the git commands, path is relative to the playbook in which it is used 135 | 136 | # Variables used in the SVN deployment strategy 137 | # Please note there was a bug in the subversion module in Ansible 1.8.x series (https://github.com/ansible/ansible-modules-core/issues/370) so it is only supported from Ansible 1.9 138 | ansistrano_svn_repo: "https://svn.company.com/project" # Location of the svn repository 139 | ansistrano_svn_branch: "trunk" # What branch from the repository to check out. 140 | ansistrano_svn_revision: "HEAD" # What revision from the repository to check out. 141 | ansistrano_svn_username: "user" # SVN authentication username 142 | ansistrano_svn_password: "Pa$$word" # SVN authentication password 143 | 144 | # Variables used in the download deployment strategy 145 | ansistrano_get_url: https://github.com/someproject/somearchive.tar.gz 146 | 147 | # Variables used in the S3 deployment strategy 148 | ansistrano_s3_bucket: s3bucket 149 | ansistrano_s3_object: s3object.tgz # Add the _unarchive suffix to the ansistrano_deploy_via if your object is a package (ie: s3_unarchive) 150 | ansistrano_s3_region: eu-west-1 151 | # Optional variables, omitted by default 152 | ansistrano_s3_aws_access_key: YOUR_AWS_ACCESS_KEY 153 | ansistrano_s3_aws_secret_key: YOUR_AWS_SECRET_KEY 154 | 155 | # Hooks: custom tasks if you need them 156 | ansistrano_before_setup_tasks_file: "{{ playbook_dir }}//my-before-setup-tasks.yml" 157 | ansistrano_after_setup_tasks_file: "{{ playbook_dir }}//my-after-setup-tasks.yml" 158 | ansistrano_before_update_code_tasks_file: "{{ playbook_dir }}//my-before-update-code-tasks.yml" 159 | ansistrano_after_update_code_tasks_file: "{{ playbook_dir }}//my-after-update-code-tasks.yml" 160 | ansistrano_before_symlink_shared_tasks_file: "{{ playbook_dir }}//my-before-symlink-shared-tasks.yml" 161 | ansistrano_after_symlink_shared_tasks_file: "{{ playbook_dir }}//my-after-symlink-shared-tasks.yml" 162 | ansistrano_before_symlink_tasks_file: "{{ playbook_dir }}//my-before-symlink-tasks.yml" 163 | ansistrano_after_symlink_tasks_file: "{{ playbook_dir }}//my-after-symlink-tasks.yml" 164 | ansistrano_before_cleanup_tasks_file: "{{ playbook_dir }}//my-before-cleanup-tasks.yml" 165 | ansistrano_after_cleanup_tasks_file: "{{ playbook_dir }}//my-after-cleanup-tasks.yml" 166 | ``` 167 | 168 | `{{ playbook_dir }}` is an Ansible variable that holds the path to the current playbook. 169 | 170 | Deploying 171 | --------- 172 | 173 | In order to deploy with Ansistrano, you need to perform some steps: 174 | 175 | * Create a new `hosts` file. Check [ansible inventory documentation](http://docs.ansible.com/intro_inventory.html) if you need help. This file will identify all the hosts where to deploy to. For multistage environments check [Multistage environments](#multistage-environment-devel-preprod-prod-etc). 176 | * Create a new playbook for deploying your app, for example, `deploy.yml` 177 | * Set up role variables (see [Role Variables](#role-variables)) 178 | * Include the `carlosbuenosvinos.ansistrano-deploy` role as part of a play 179 | * Run the deployment playbook 180 | 181 | ```ansible-playbook -i hosts deploy.yml``` 182 | 183 | If everything has been set up properly, this command will create the following approximate directory structure on your server. Check how the hosts folder structure would look like after one, two and three deployments. 184 | 185 | ``` 186 | -- /var/www/my-app.com 187 | |-- current -> /var/www/my-app.com/releases/20100509145325 188 | |-- releases 189 | | |-- 20100509145325 190 | |-- shared 191 | ``` 192 | 193 | ``` 194 | -- /var/www/my-app.com 195 | |-- current -> /var/www/my-app.com/releases/20100509150741 196 | |-- releases 197 | | |-- 20100509150741 198 | | |-- 20100509145325 199 | |-- shared 200 | ``` 201 | 202 | ``` 203 | -- /var/www/my-app.com 204 | |-- current -> /var/www/my-app.com/releases/20100512131539 205 | |-- releases 206 | | |-- 20100512131539 207 | | |-- 20100509150741 208 | | |-- 20100509145325 209 | |-- shared 210 | ``` 211 | 212 | ### Serial deployments 213 | 214 | To prevent different timestamps when deploying to several servers using the [`serial`](http://docs.ansible.com/playbooks_delegation.html#rolling-update-batch-size) option, you should set the `ansistrano_release_version` variable. 215 | 216 | ```ansible-playbook -i hosts -e "ansistrano_release_version=`date -u +%Y%m%d%H%M%SZ`" deploy.yml``` 217 | 218 | 219 | Rolling back 220 | ----------- 221 | 222 | In order to rollback with Ansistrano, you need to set up the deployment and run the rollback playbook. 223 | 224 | ```ansible-playbook -i hosts rollback.yml``` 225 | 226 | If you try to rollback with zero or one releases deployed, an error will be raised and no actions performed. 227 | 228 | Variables you can tune in rollback role are less than in deploy one: 229 | 230 | ```yaml 231 | - vars: 232 | ansistrano_deploy_to: "/var/www/my-app" # Base path to deploy to. 233 | ansistrano_version_dir: "releases" # Releases folder name 234 | ansistrano_current_dir: "current" # Softlink name. You should rarely changed it. 235 | 236 | # Hooks: custom tasks if you need them 237 | ansistrano_before_symlink_tasks_file: "{{ playbook_dir }}//my-before-symlink-tasks.yml" 238 | ansistrano_after_symlink_tasks_file: "{{ playbook_dir }}//my-after-symlink-tasks.yml" 239 | ansistrano_before_cleanup_tasks_file: "{{ playbook_dir }}//my-before-cleanup-tasks.yml" 240 | ansistrano_after_cleanup_tasks_file: "{{ playbook_dir }}//my-after-cleanup-tasks.yml" 241 | ``` 242 | 243 | Multistage environment (devel, preprod, prod, etc.) 244 | --------------------------------------------------- 245 | 246 | If you want to deploy to different environments such as devel, preprod and prod, it's recommended to create different hosts files. When done, you can specify a different host file when running the deployment playbook using the **-i** parameter. On every host file, you can specify different users, password, connection parameters, etc. 247 | 248 | ```ansible-playbook -i hosts_devel deploy.yml``` 249 | 250 | ```ansible-playbook -i hosts_preprod deploy.yml``` 251 | 252 | ```ansible-playbook -i hosts_prod deploy.yml``` 253 | 254 | Hooks: Custom tasks 255 | ------------------- 256 | 257 | You will typically need to reload your webserver after the `Symlink` step, or download your dependencies before `Code update` or even do it in production before the `Symlink`. So, in order to perform your custom tasks you have some hooks that Ansistrano will execute before and after each of the main 3 steps. **This is the main benefit against other similar deployment roles.** 258 | 259 | ``` 260 | -- /my-local-machine/my-app.com 261 | |-- hosts 262 | |-- deploy.yml 263 | |-- my-custom-tasks 264 | | |-- before-code-update.yml 265 | | |-- after-code-update.yml 266 | | |-- before-symlink.yml 267 | | |-- after-symlink.yml 268 | | |-- before-cleanup.yml 269 | | |-- after-cleanup.yml 270 | ``` 271 | 272 | For example, in order to restart apache after `Symlink` step, we'll add in the `after-symlink.yml` 273 | 274 | ``` 275 | - name: Restart Apache 276 | service: name=httpd state=reloaded 277 | ``` 278 | 279 | * **Q: Where would you add sending email notification after a deployment?** 280 | * **Q: (for PHP and Symfony developers) Where would you clean the cache?** 281 | 282 | You can specify a custom tasks file for before and after every step using `ansistrano_before_*_tasks_file` and `ansistrano_after_*_tasks_file` role variables. See "Role Variables" for more information. 283 | 284 | Variables in custom tasks 285 | ------------------------- 286 | 287 | When writing your custom tasks files you may need some variables that Ansistrano makes available to you: 288 | 289 | * ```{{ ansistrano_release_path.stdout }}```: Path to current deployment release (probably the one you are going to use the most) 290 | * ```{{ ansistrano_releases_path.stdout }}```: Path to releases folder 291 | * ```{{ ansistrano_shared_path.stdout }}```: Path to shared folder (where common releases assets can be stored) 292 | * ```{{ ansistrano_release_version }}```: Relative directory name for the release (by default equals to the current timestamp in UTC timezone) 293 | 294 | Pruning old releases 295 | -------------------- 296 | 297 | In continuous delivery environments, you will possibly have a high number of releases in production. Maybe you have tons of space and you don't mind, but it's common practice to keep just a custom number of releases. 298 | 299 | After the deployment, if you want to remove old releases just set the `ansistrano_keep_releases` variable to the total number of releases you want to keep. 300 | 301 | Let's see three deployments with an `ansistrano_keep_releases: 2` configuration: 302 | 303 | ``` 304 | -- /var/www/my-app.com 305 | |-- current -> /var/www/my-app.com/releases/20100509145325 306 | |-- releases 307 | | |-- 20100509145325 308 | |-- shared 309 | ``` 310 | 311 | ``` 312 | -- /var/www/my-app.com 313 | |-- current -> /var/www/my-app.com/releases/20100509150741 314 | |-- releases 315 | | |-- 20100509150741 316 | | |-- 20100509145325 317 | |-- shared 318 | ``` 319 | 320 | ``` 321 | -- /var/www/my-app.com 322 | |-- current -> /var/www/my-app.com/releases/20100512131539 323 | |-- releases 324 | | |-- 20100512131539 325 | | |-- 20100509150741 326 | |-- shared 327 | ``` 328 | 329 | See how the release `20100509145325` has been removed. 330 | 331 | Example Playbook 332 | ---------------- 333 | 334 | In the folder, `example` you can check an example project that shows how to deploy a small application with Ansistrano. 335 | 336 | In order to run it, you will need to have Vagrant and the ansistrano roles installed. Please check https://www.vagrantup.com for more information about Vagrant and our Installation section. 337 | 338 | ``` 339 | $ cd example/my-playbook 340 | $ vagrant up 341 | $ ansible-playbook -i hosts deploy.yml 342 | ``` 343 | 344 | And after running these commands, the index.html located in the `my-app` folder will be deployed to both vagrant boxes 345 | 346 | In order to test the rollback playbook, you will need to run deploy.yml at least twice (so that there is something to rollback to). And once this is done, you only need to run 347 | 348 | ``` 349 | $ ansible-playbook -i hosts rollback.yml 350 | ``` 351 | 352 | You can check more advanced examples inside the test folder which are run against Travis-CI 353 | 354 | Sample projects 355 | --------------- 356 | 357 | We have added Ansistrano support for other projects we are working on. 358 | 359 | * LastWishes: Domain-Driven Design PHP Sample App: https://github.com/dddinphp/last-wishes 360 | 361 | As an example, see the execution log of the LastWishes deployment: 362 | 363 | ``` 364 | PLAY [Deploy last wishes app to my server] ************************************ 365 | 366 | GATHERING FACTS *************************************************************** 367 | ok: [quepimquepam.com] 368 | 369 | TASK: [carlosbuenosvinos.ansistrano-deploy | Ensure deployment base path exists] *** 370 | ok: [quepimquepam.com] 371 | 372 | TASK: [carlosbuenosvinos.ansistrano-deploy | Ensure releases folder exists] *** 373 | ok: [quepimquepam.com] 374 | 375 | TASK: [carlosbuenosvinos.ansistrano-deploy | Ensure shared elements folder exists] *** 376 | ok: [quepimquepam.com] 377 | 378 | TASK: [carlosbuenosvinos.ansistrano-deploy | Get release timestamp] *********** 379 | changed: [quepimquepam.com] 380 | 381 | TASK: [carlosbuenosvinos.ansistrano-deploy | Get release path] **************** 382 | changed: [quepimquepam.com] 383 | 384 | TASK: [carlosbuenosvinos.ansistrano-deploy | Get releases path] *************** 385 | changed: [quepimquepam.com] 386 | 387 | TASK: [carlosbuenosvinos.ansistrano-deploy | Get shared path (in rsync case)] *** 388 | changed: [quepimquepam.com] 389 | 390 | TASK: [carlosbuenosvinos.ansistrano-deploy | Rsync application files to remote shared copy (in rsync case)] *** 391 | changed: [quepimquepam.com -> 127.0.0.1] 392 | 393 | TASK: [carlosbuenosvinos.ansistrano-deploy | Deploy existing code to servers] *** 394 | changed: [quepimquepam.com] 395 | 396 | TASK: [carlosbuenosvinos.ansistrano-deploy | Deploy existing code to remote servers] *** 397 | skipping: [quepimquepam.com] 398 | 399 | TASK: [carlosbuenosvinos.ansistrano-deploy | Update remote repository] ******** 400 | skipping: [quepimquepam.com] 401 | 402 | TASK: [carlosbuenosvinos.ansistrano-deploy | Export a copy of the repo] ******* 403 | skipping: [quepimquepam.com] 404 | 405 | TASK: [carlosbuenosvinos.ansistrano-deploy | Deploy code from to servers] ***** 406 | skipping: [quepimquepam.com] 407 | 408 | TASK: [carlosbuenosvinos.ansistrano-deploy | Copy release version into REVISION file] *** 409 | changed: [quepimquepam.com] 410 | 411 | TASK: [carlosbuenosvinos.ansistrano-deploy | Touches up the release code] ***** 412 | changed: [quepimquepam.com] 413 | 414 | TASK: [carlosbuenosvinos.ansistrano-deploy | Change softlink to new release] *** 415 | changed: [quepimquepam.com] 416 | 417 | TASK: [carlosbuenosvinos.ansistrano-deploy | Reload Apache] ******************* 418 | changed: [quepimquepam.com] 419 | 420 | TASK: [carlosbuenosvinos.ansistrano-deploy | Clean up releases] *************** 421 | skipping: [quepimquepam.com] 422 | 423 | PLAY RECAP ******************************************************************** 424 | quepimquepam.com : ok=14 changed=10 unreachable=0 failed=0 425 | ``` 426 | 427 | They're talking about us 428 | ------------------------ 429 | 430 | * [Pablo Godel - Deploying Symfony - Symfony Cat 2016](https://youtu.be/K2bBhrkmpSg?t=26m) 431 | * [https://www.artansoft.com/2016/05/deploy-de-proyectos-php-ansistrano/](https://www.artansoft.com/2016/05/deploy-de-proyectos-php-ansistrano/) 432 | * [http://alexmoreno.net/ansistrano-deploying-drupal-ansible](http://alexmoreno.net/ansistrano-deploying-drupal-ansible) 433 | * [http://www.ricardclau.com/2015/10/deploying-php-applications-with-ansistrano/](http://www.ricardclau.com/2015/10/deploying-php-applications-with-ansistrano/) 434 | * [http://es.slideshare.net/OrestesCA/ansible-intro-ansible-barcelona-user-group-june-2015](http://es.slideshare.net/OrestesCA/ansible-intro-ansible-barcelona-user-group-june-2015) 435 | * [http://carlosbuenosvinos.com/deploying-symfony-and-php-apps-with-ansistrano/](http://carlosbuenosvinos.com/deploying-symfony-and-php-apps-with-ansistrano/) 436 | * [https://www.youtube.com/watch?v=CPz5zPzzMZE](https://www.youtube.com/watch?v=CPz5zPzzMZE) 437 | * [https://github.com/cbrunnkvist/ansistrano-symfony-deploy](https://github.com/cbrunnkvist/ansistrano-symfony-deploy) 438 | * [https://www.reddit.com/r/ansible/comments/2ezzz5/rapid_rollback_with_ansible/](https://www.reddit.com/r/ansible/comments/2ezzz5/rapid_rollback_with_ansible/) 439 | 440 | 441 | License 442 | ------- 443 | 444 | MIT 445 | 446 | Other resources 447 | --------------- 448 | 449 | * [Thoughts on deploying with Ansible](http://www.future500.nl/articles/2014/07/thoughts-on-deploying-with-ansible/) 450 | --------------------------------------------------------------------------------