Buy

Extensions, php.ini & lineinfile

PHP, MySQL and Nginx: check, check and check! But we've only installed php7.1-cli. An in reality, we need a lot more than that! What about php7.1-mysql or php7.1-fpm? Yep, we need those friendly extensions... and a few others.

Looping: with_items

Wonderfully, on Ubuntu, these are all installed via apt-get. We could copy and paste the php7.1-cli task over and over and over again for each package. Or, to level-up our Ansible-awesomeness, we can loop!

Let's see how: change the task's name to Install PHP packages:

56 lines ansible/playbook.yml
---
- hosts: vb
tasks:
... lines 5 - 44
- name: Install PHP packages
... lines 46 - 56

Then, instead of php7.1-cli, add the very cryptic "{{ item }}":

56 lines ansible/playbook.yml
---
- hosts: vb
tasks:
... lines 5 - 44
- name: Install PHP packages
... line 46
apt:
name: "{{ item }}"
... lines 49 - 56

Finish it with a new with_items key after the apt module. This gets a big list of the stuff we want: php7.1-cli, php7.1-curl, ice cream, php7.1 -fpm, php7.1-intl, a pony and php7.1-mysql. If we need more goodies later, we can add them:

56 lines ansible/playbook.yml
---
- hosts: vb
tasks:
... lines 5 - 44
- name: Install PHP packages
become: true
apt:
name: "{{ item }}"
state: latest
with_items:
- php7.1-cli
- php7.1-curl
- php7.1-fpm
- php7.1-intl
- php7.1-mysql

Flip over to your terminal and try it!

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

Using Jinja

While we wait, let's check out the code we just wrote. For the first time, we're seeing Ansible's templating language in action! Yep, whenever you see {{ }}, you're writing Jinja code - a Python templating language... which - guess what - is more or less identical to Twig. Win!

In this case, we're opening up Jinja to print a variable called item. That works because Ansible has this nice with_items loop feature. And notice, this is not special to the apt module - it'll work anywhere.

Oh, and those quotes are important:

56 lines ansible/playbook.yml
---
- hosts: vb
tasks:
... lines 5 - 44
- name: Install PHP packages
... line 46
apt:
name: "{{ item }}"
... lines 49 - 56

Quoting is usually optional in YAML. But if a value starts with {{, it's mandatory.

Head back to the terminal. Yes! Celebrate! PHP extensions installed! I'll move to my third tab where I've already run vagrant ssh to get into the VM. Check for the MySQL extension:

php -i | grep mysql

See that PDO MySQL stuff? That proves it worked!

Re-run that command again and look for timezone:

php -i | grep timezone

Tweaking php.ini settings with lineinfile

Hmm, it says date.timezone no value, which means that it is not set in php.ini. Since PHP 7.0, that's not a huge deal - in PHP 5 this caused an annoying warning. But, I still want to make sure it's set.

Question number 1 is... where the heck is my php.ini file? Answer, run:

php --ini

There it is /etc/php/7.1/cli/php.ini. Open that up in vim and hit /timezone, enter, to find that setting:

[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
;date.timezone =

Ok, it's commented-out right now. We want Ansible to uncomment that line and set it to UTC. Quit with Escape, :q, enter.

So how can we tell Ansible to make a change right in the middle of a file? Of course, Ansible has a module just for that! Search for the "Ansible lineinfile" module. Ah, ha!

Ensure a particular line is in a file, or replace an existing line

Let's check out the options! The only required one is path - the file we need to change. Then, we can use the regexp option to find the target line and line as the value to replace it with.

Before we do this, look back at the path option. It says that before Ansible 2.3, this was called dest, destfile or name instead of path. What version do we have? Find out:

ansible --version

We're on 2.1! So instead of path, we need to use dest. This is something to watch out for... because at the time of this recording, Ansible 2.3 isn't even released yet! For some reason, Ansible always shows the docs for its latest, unreleased version.

Let's rock! Add the new task: Set date.timezone for CLI. Add become: true and use the lineinfile module:

70 lines ansible/playbook.yml
---
- hosts: vb
tasks:
... lines 5 - 56
- name: Set date.timezone for CLI
become: true
lineinfile:
... lines 60 - 70

For options, pass it dest: /etc/php/7.1/cli/php.ini and regexp: date.timezone =:

70 lines ansible/playbook.yml
---
- hosts: vb
tasks:
... lines 5 - 56
- name: Set date.timezone for CLI
become: true
lineinfile:
dest: /etc/php/7.1/cli/php.ini
regexp: "date.timezone ="
... lines 62 - 70

We're not leveraging any regex here: this will simply find a line that contains date.timezone =. Finally, add line: date.timezone = UTC:

70 lines ansible/playbook.yml
---
- hosts: vb
tasks:
... lines 5 - 56
- name: Set date.timezone for CLI
become: true
lineinfile:
dest: /etc/php/7.1/cli/php.ini
regexp: "date.timezone ="
line: "date.timezone = UTC"
... lines 63 - 70

With the line option, the entire line will be replaced - not just the part that was matched from the regexp option. That means the comment at the beginning of the line will be removed.

Tip

There is also an ini_file module, which makes modifying .ini files even easier. For an example, see: http://bit.ly/knpu-ini-module

Now, copy that entire task and paste it. In Ubuntu, there are 2 different php.ini files: rename this one to Set date.timezone for FPM. Change the dest path from cli/ to fpm/. That's the correct path inside the VM:

70 lines ansible/playbook.yml
---
- hosts: vb
tasks:
... lines 5 - 63
- name: Set date.timezone for FPM
become: true
lineinfile:
dest: /etc/php/7.1/fpm/php.ini
regexp: "date.timezone ="
line: "date.timezone = UTC"

Run it!

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

Before that finishes, flip back to the VM and check the timezone setting:

php -i | grep timezone

No value. Then... once it finishes... try it again:

php -i | grep timezone

Got it! UTC! I'll open up my php.ini to be sure... and...

[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = UTC

Yes! The line was perfectly replaced.

Say hello to lineinfile: your Swiss army knife for updating configuration files.

Leave a comment!

  • 2017-08-14 Victor Bocharsky

    What about setting it for both FPM and CLI - take a look at this trick:


    - name: Set date.timezone for PHP
    become: true
    ini_file:
    dest: "/etc/php/7.0/{{ item.env }}/php.ini"
    section: Date
    option: "{{ item.option }}"
    value: "{{ item.value }}"
    with_items:
    - { env: fpm, option: date.timezone, value: Europe/Berlin }
    - { env: cli, option: date.timezone, value: Europe/Berlin }
    - { env: fpm, option: max_execution_time, value: 60 }
    - { env: cli, option: max_execution_time, value: 60 }
    # ...

    This is the most simple solution of doing it in a single task I think. Or, yes, use another one task for CLI.

    Cheers!

  • 2017-08-14 Victor Bocharsky

    Oh, thanks for this update! Looks like important one

    Cheers!

  • 2017-08-14 Victor Bocharsky

    Hey Karsten,

    Good question! Actually, Id say it would be better. But why didn't we use it in our screencasts? Well, the simplest answer is because... I see this module for the first time :p So, I think we'll add a note about this module, but you're right, if there's a specific module for some kind of tweaks - better to use that specific module instead of re-inventing the wheel ;)

    Thanks for sharing it with us!

    Cheers!

  • 2017-08-11 Karsten Frohwein

    Okay my last entry is sadly wrong. Every setting than goes to the "Date" section...


    - name: Set date.timezone for PHP
    become: true
    ini_file:
    dest: /etc/php/7.0/fpm/php.ini
    section: "{{ item.section }}"
    option: "{{ item.option }}"
    value: "{{ item.value }}"
    with_items:
    - { section: Date, option: date.timezone, value: Europe/Berlin }
    - { section: PHP, option: max_execution_time, value: 60 }
    - { section: PHP, option: memory_limit, value: 512M }
  • 2017-08-11 Karsten Frohwein

    Hey Ho,

    thanks for the php.ini line replacement. But wouldn't it be better to use:

    http://docs.ansible.com/ans...

    Like:


    - name: Set date.timezone for PHP
    become: true
    ini_file:
    dest: /etc/php/7.0/fpm/php.ini
    section: Date
    option: "{{ item.option }}"
    value: "{{ item.value }}"
    with_items:
    - { option: date.timezone, value: Europe/Berlin }
    - { option: max_execution_time, value: 60 }
    - { option: memory_limit, value: 512M }

    And what if I want to set it to PHP FPM and CLI? More Irems? Some Python way I don't know? One more use of ini_file?

    Cheers Karsten