Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

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!

24
Login or Register to join the conversation
Beis Avatar
Beis Avatar Beis | posted 4 years ago | edited

Hi Guys, I'm on a trouble with installing composer on EC2. I'm writting here because it's error related on previous comments:
skipping: [192.168.33.10]<br />fatal: [18.203.185.87]: FAILED! => {"changed": true, "failed": true, "rc": 1, "stderr": "Shared connection to 18.203.185.87 closed.\r\n", "stdout": "/home/ubuntu/.ansible/tmp/ansible-tmp-1566331749.55-41571300702954/install_composer.sh: 4: /home/ubuntu/.ansible/tmp/ansible-tmp-1566331749.55-41571300702954/install_composer.sh: php: not found\r\n/home/ubuntu/.ansible/tmp/ansible-tmp-1566331749.55-41571300702954/install_composer.sh: 5: /home/ubuntu/.ansible/tmp/ansible-tmp-1566331749.55-41571300702954/install_composer.sh: php: not found\r\nERROR: Invalid installer signature\r\nrm: cannot remove 'composer-setup.php': No such file or directory\r\n", "stdout_lines": ["/home/ubuntu/.ansible/tmp/ansible-tmp-1566331749.55-41571300702954/install_composer.sh: 4: /home/ubuntu/.ansible/tmp/ansible-tmp-1566331749.55-41571300702954/install_composer.sh: php: not found", "/home/ubuntu/.ansible/tmp/ansible-tmp-1566331749.55-41571300702954/install_composer.sh: 5: /home/ubuntu/.ansible/tmp/ansible-tmp-1566331749.55-41571300702954/install_composer.sh: php: not found", "ERROR: Invalid installer signature", "rm: cannot remove 'composer-setup.php': No such file or directory"]}

As you can see, on vagrant machine it is working fine, but on EC2 it is not correct signature... how I can fix that?
It is a 16.04 ubuntu ec2 instance.
Thanks!

Reply

Hey Alex,

Do you have PHP installed on your EC2? It sounds like you don't have "php" executable command. Could you manually ssh-ed to the EC2 and run "php --version". If you don't have a PHP yet - you need to install it first, otherwise "install_composer.sh" won't work I think.

Cheers!

Reply
Beis Avatar

Ups, that was! my fault! I was executing the command with -t deploy... so provisioning tasks were called anymore...Thanks!

Reply
Jeffrey C. Avatar
Jeffrey C. Avatar Jeffrey C. | posted 4 years ago | edited

Hey,
Once i run the playbook i get the following error

`fatal: [192.168.33.10]: FAILED! => {"changed": true, "cmd": ["mv", "composer.phar", "/user/local/bin/composer"], "delta": "0:00:00.005364", "end": "2018-12-27 08:50:50.294032", "msg": "non-zero return code", "rc": 1, "start": "2018-12-27 08:50:50.288668", "stderr": "mv: cannot move 'composer.phar' to '/user/local/bin/composer': No such file or directory", "stderr_lines": ["mv: cannot move 'composer.phar' to '/user/local/bin/composer': No such file or directory"], "stdout": "", "stdout_lines": []}

to retry, use: --limit @/Users/emin/Ansible/ansible/playbook.retry

`

I did it like you guys told me.

Cheers!

Reply

Hey Emin,

Looks like you don't have "composer.phar" file. Could you make sure you download the "composer.phar" to successfully. And make sure you name the downloaded file as "composer.phar".

Cheers!

Reply
Jeffrey C. Avatar
Jeffrey C. Avatar Jeffrey C. | Jeffrey C. | posted 4 years ago | edited

UPDATE: i made a new task to create a directory so it has one but once i did that
i get the following error:
`
fatal: [192.168.33.10]: FAILED! => {"changed": false, "msg": "file (/usr/local/bin/composer) is absent, cannot continue", "path": "/usr/local/bin/composer", "state": "absent"}

to retry, use: --limit @/Users/emin/Ansible/ansible/playbook.retry`

i think it does this because the folder already exist.

Reply

Hey Emin,

Wait, what folder exactly did you create? I suppose the problem was that you didn't have composer.phar file, so the problem was not in folder.

Cheers!

Reply
Jeffrey C. Avatar
Jeffrey C. Avatar Jeffrey C. | Victor | posted 4 years ago

Het victor,

When i checked my vagrant box i saw that i did not have the directory "/usr/local/bin/composer" and i did have the composer.phar installed but i created a task just to make a directory so it could find the directory.

Cheers!

Reply

Hey Emin,

But that's incorrect! The "/usr/local/bin/composer" is not a directory but file. Basically, with that command we rename "composer.phar" file to "composer" (i.e. without ".phar" extension) and move it to the "/usr/local/bin/" directory that I bet you have. So, the problem is that command was not able to find composer.phar file to do this operation. Now you need to remove that "/usr/local/bin/composer" directory you created and make sure that "composer.phar" file exists. The only folder you need to have is "/usr/local/bin/" only.

Cheers!

Reply
Jeffrey C. Avatar
Jeffrey C. Avatar Jeffrey C. | Victor | posted 4 years ago

Hey Victor,

Yes it works now but to run composer in need to type /usr/local/bin/composer to use it the composer and in the video it's only composer and it works so that's where i get confused a bit.

Cheers!

Reply

Hey Emin,

Hm, it depends on your OS probably. The "/usr/local/bin/" folder should be in the PATH env variable, so you can it globally. In some systems it might be "/usr/bin/". You can try to move the file there, i.e. the path should be "/usr/bin/composer". Or you need to add "/usr/local/bin/" to the PATH env var. Actually, just run "echo $PATH" and you'll see what global executable paths you have. Btw, you said that executing "/usr/local/bin/composer" works for you, but what error do you see when try to execute just "composer"?

Cheers!

Reply
Jeffrey C. Avatar
Jeffrey C. Avatar Jeffrey C. | Victor | posted 4 years ago

Hey Victor,

No command 'composer' found, did you mean:
Command 'compose' from package 'mime-support' (main)
composer: command not found

but in the direcotry "/user/local/bin/composer" i see the composer and composer.phar both in there. so i'm getting a bit confused now with this.

Cheers!

Reply

Hey Emin,

Wait wait, what path exactly? :) you wrote "/user/local/bin/composer" but it's incorrect, it should be "/usr/local/bin/composer" - notice that usr! And what about "echo $PATH" output??

Cheers!

Reply
Jeffrey C. Avatar
Jeffrey C. Avatar Jeffrey C. | Victor | posted 4 years ago

Hey Victor,

OMG really it works now i hate it when i miss type something and don't know where the error is comming from
:(. but yeah thank you man i works perfectly now. I need a break now from coding for an hour.

Cheers man !

Reply

Hey Emin,

Lol, I mentioned that *exact* path a few times! :) No problem! I'm glad we found the problem and it works now ;)

Cheers!

Reply
Default user avatar
Default user avatar jian su | posted 5 years ago

Hi guys:

Is there way we can roll back the task. Say I don't need composer, and can I roll back to previous state. I tried to delete the task and run again, composer still there/:

Reply

Hey jian su!

There's no native rollback in Ansible. As you know, the purpose of the tasks/playbook is to put your server in a specific "state". If you don't have any tasks related to Composer suddenly, then your playbook basically doesn't have any opinion about the "state" of Composer. It does nothing :). If you did really care about the state or something, then you should actually add a task to make sure it's not installed. I'm not suggesting you do this - because you probably don't care enough - but that's the official answer :). For example, the apt module allows you to set the state to absent... which you would use if you actually wanted to make sure that something was not installed.

And of course, you can always start from a fresh server. But any specific rollbacks are up to you (and kind of need to be - Ansible wouldn't natively know how to rollback the Composer installation anyways).

Cheers!

Reply
Default user avatar
Default user avatar gstanto | posted 5 years ago

I'm hitting a weird snag with the script module. Note, I am on windows linux subsystem. When I run the Install Composer step outlined above, I'm getting this error:

TASK [Download Composer] ********************************************************fatal: [192.168.33.20]: FAILED! => {"changed": true, "msg": "non-zero return code", "rc": 127, "stderr": "Shared connection to 192.168.33.20 closed.\r\n", "stdout": "/bin/sh: 1: /home/vagrant/.ansible/tmp/ansible-tmp-1521308840.85-113836180865947/install_composer.sh: not found\r\n", "stdout_lines": ["/bin/sh: 1: /home/vagrant/.ansible/tmp/ansible-tmp-1521308840.85-113836180865947/install_composer.sh: not found"]}
to retry, use: --limit @/mnt/c/Users/Gary/Development/ansible/playbook.retry

Been searching everywhere, but I can't seem to get it to run. Note that .ansible/tmp directory IS being written to. Not sure why it would not include the sh file

Ansible 2.4.3, Vagrant 2.0.3.

Reply
Default user avatar

UPDATE: Windows strikes again. I attempted to run the script manually in vagrant command line and got the error:
/bin/bash^M: bad interpreter. Apparently this can happen when you write shell files on Windows (new one to me)

I recreated the install_composer file in vim instead of the ide and worked just fine.

credit to this guy:
https://stackoverflow.com/q...

1 Reply

Hey gstanto ,

Glad you got it working! And thanks for sharing this info for our Windows friends ;) Btw, the answer seems pretty popular on GitHub, so I bet many users get this error.

Cheers!

Reply
Default user avatar
Default user avatar aksfjaw | Victor | posted 2 years ago | edited

Ran into the same problem.

I'd add that after converting the file to LF from CRLF (there's a button for that at the bottom right in phpStorm) you can create a .gitattributes file to tell git to always store files in ansible/scripts/ using LF, otherwise you'll have this problem every time you check out your repository. See https://stackoverflow.com/a/63736688/1419007

Reply

Hey Aksfjaw,

Thank you for sharing your alternative solution! Sounds valid to me

Cheers!

Reply
liviu Avatar

Hi,
How do we add an user to an ubuntu server with Ansible, other than root user, which can run composer without issues? Composer warns about running "composer install" command as root.
My server is an ubuntu server with root as default and no other users created. The login is made through SSH as root.

L.E.: I fixed it by creating a new user on ubuntu server manually then modified the Ansible playbook to use that specific user.

-1 Reply

Hey Liviu,

Good workaround, well done! In case you still want to automate this, let's see what options you have.

Remember, if you don't know a specific Ansible command that you can use for doing things you want - you can always use "command" or "shell" to execute *whatever* console commands you want manually. So, just google how to create users in Ubuntu servers with a CLI command and use it :)

Though, if you're interested in creating users with Ansible - Ansible really has a specific command for this called "user" :) See the docs about it: https://docs.ansible.com/an... - you can do a lot of things with it, look at examples to start.

I hope this helps!

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

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
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    },
    "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
    }
}
userVoice