Installing Composer & the script Module

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With our project cloned, the next step is obvious: use Composer to install our dependencies! Actually, we used the Composer module earlier from the command line. Google again for "Ansible Modules" and find the "All Modules" page. I'll find the composer module and open that in a new tab.

This module is really easy... except for one problem. Under Requirements, it says that Composer already needs to be installed. We have not done that yet... and, unfortunately, it can't be installed with apt-get.

Installing Composer Programmatically?

So how do you install it? Check out https://getcomposer.org and click "Download".

Normally, we just paste these lines into our terminal and celebrate! But... there's this problematic fine print at the bottom:

Do not redistribute the install code. It will change for every version of the install.

Huh. Composer includes a bit of built-in security: a sha hash to make sure that the installer hasn't been tampered with. If we tried to use these 4 commands in Ansible, it would work... for awhile. But next time the installer is updated, and that sha changed... it would stop working.

What to do? Check out that how to install Composer programmatically link. Eureka: a shell script that will safely download the latest version of Composer. The end result is a composer.phar file wherever we run this script from.

Installing Composer with script

Our mission is clear: somehow, execute this shell script via Ansible. But before we do that, near the top, add one new task: Install low-level utilities:

---
- hosts: vb
... lines 3 - 6
tasks:
... lines 8 - 24
- name: Install low-level utilities
... lines 26 - 109

Here, use the apt module and the with_items syntax to install zip and unzip:

---
- hosts: vb
... lines 3 - 6
tasks:
... lines 8 - 24
- name: Install low-level utilities
become: true
apt:
name: "{{ item }}"
with_items:
- zip
- unzip
... lines 32 - 109

Without these, Composer will run really slowly and you'll blame Jordi when you should be thanking him.

Now, back to our main job: how can we execute a script on a host? Why, with... the script module of course!

Runs a local script on a remote node after transferring it

Neato! We just point it at a local script, and it takes care of the rest. Go copy the script and, in our ansible directory, create a new scripts directory and a new file called install_composer.sh. Paste the code there:

#!/bin/sh
EXPECTED_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig)
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');")
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
then
>&2 echo 'ERROR: Invalid installer signature'
rm composer-setup.php
exit 1
fi
php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT

Back in the playbook, at the bottom, create a new task: Download Composer:

---
- hosts: vb
... lines 3 - 6
tasks:
... lines 8 - 96
- name: Download Composer
... lines 98 - 109

Use the script module. Then, the easiest way to use this is to literally put the script filename on the same line as the module name: script: scripts/install_composer.sh:

---
- hosts: vb
... lines 3 - 6
tasks:
... lines 8 - 96
- name: Download Composer
script: scripts/install_composer.sh
... lines 99 - 109

Actually, every module can be used with a one-line syntax like this... but since line breaks are pretty cheap these days, I usually organize things a bit more.

Thanks to this task, we'll have a new composer.phar file in our home directory, which is where this task - well, all tasks - are running. But that's not enough: we need to move this to /usr/local/bin/composer.

Moving Composer Globally

Create another task: Move Composer globally. This time, use become: true and use the command module:

---
- hosts: vb
... lines 3 - 6
tasks:
... lines 8 - 99
- name: Move Composer globally
become: true
... lines 102 - 109

In your browser, go find the command module. Like with script, command has a short syntax. We'll say: command: mv composer.phar to /usr/local/bin/composer:

---
- hosts: vb
... lines 3 - 6
tasks:
... lines 8 - 99
- name: Move Composer globally
become: true
command: mv composer.phar /usr/local/bin/composer
... lines 103 - 109

If you're a little surprised that I'm using the command module instead of some built-in file or move module... me too! In general, you should always look for a built-in module first: they're always more powerful than using command. But sometimes, like with moving files, command is the right tool for the job.

Add one more task to make sure the file is executable: "Set Permissions on Composer" with become: true:

---
- hosts: vb
... lines 3 - 6
tasks:
... lines 8 - 103
- name: Set permissions on Composer
become: true
... lines 106 - 109

Remember, Ansible is all about state. The job of the file module isn't really to create files or symlinks. Instead, it's to make sure that they exist and have the right permissions. In this case, we're going to take advantage of the mode option to guarantee that the file is executable.

In the playbook, use the file module, set path to /usr/local/bin/composer and mode to "a+x" to guarantee that all users have executable permission:

---
- hosts: vb
... lines 3 - 6
tasks:
... lines 8 - 103
- name: Set permissions on Composer
become: true
file:
path: /usr/local/bin/composer
mode: "a+x"

Oh, and make sure the file you created is install_composer.sh.

Time to give this a try. Find your main machine's terminal and run the playbook!

ansible-playbook ansible/playbook.yml -i ansible/hosts.ini

Back on the VM, I'm in my home directory. And right now, it's empty. But if you're really fast, you can see the installation script doing its work. There it is: composer-setup.phar, composer-temp.phar, composer.phar and then it's gone once our task moves it. Yes!

And finally, we can type composer. Let's install some dependencies already!

Leave a comment!

This tutorial is built using an older version of Symfony, but the core concepts of Ansible are still valid. New versions of Ansible may contain some features that we don't use here.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.12
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.0
        "snc/redis-bundle": "^2.0", // 2.0.0
        "predis/predis": "^1.1" // v1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.8
        "symfony/phpunit-bridge": "^3.0", // v3.1.4
        "doctrine/data-fixtures": "^1.1", // 1.3.3
        "hautelook/alice-bundle": "^1.3" // v1.4.1
    }
}