czwartek, 8 sierpnia 2019

Testowanie roli Ansible za pomocą Molecule

Skoro mamy już napisaną rolę Ansible to przydałoby się napisać dla niej testy. Ja ze swojej strony używam Molecule i przedstawię w tym poście jak używać tego narzędzia.

Molecule możemy zainstalować jako moduł Pythona wykorzystując pip:
vagrant@ubuntu-bionic:~$ sudo pip install molecule
Aktualnie nasza rola wygląda tak:
vagrant@ubuntu-bionic:~$ tree ansible-base/
ansible-base/
├── LICENSE
├── README.md
├── defaults
│   └── main.yml
├── meta
│   └── main.yml
└── tasks
    └── main.yml
3 directories, 5 files
Chcemy testować w kontenerze dockerowym więc wcześniej musimy Dockera zainstalować. Oczywiście nie będę tutaj tego omawiał. Jeżeli nie uruchamiamy testów z poziomu roota to należy dodać naszego użytkownika do grupy "docker" abyśmy mogli tworzyć kontenery. Potrzebujemy jeszcze moduł pythonowy, który umożliwi nam komunikację z Dockerem:
vagrant@ubuntu-bionic:~/ansible-base/molecule/default$ sudo pip install docker-py
Gdy już mamy Dockera w celu inicjalizacji scenariusza testów wchodzimy do katalogu z rolą i wydajemy komendę
vagrant@ubuntu-bionic:~/ansible-base$ molecule init scenario --role-name ansible-base --driver-name docker
--> Initializing new scenario default...
Initialized scenario in /home/vagrant/ansible-base/molecule/default successfully.
Aktualnie drzewo katalogów wygląda tak:
vagrant@ubuntu-bionic:~$ tree ansible-base/
ansible-base/
├── LICENSE
├── README.md
├── defaults
│   └── main.yml
├── meta
│   └── main.yml
├── molecule
│   └── default
│       ├── Dockerfile.j2
│       ├── INSTALL.rst
│       ├── molecule.yml
│       ├── playbook.yml
│       └── tests
│           ├── test_default.py
│           └── test_default.pyc
└── tasks
    └── main.yml
6 directories, 11 files
Przypuścmy, że chcemy testować naszą rolę w systemie Ubuntu 16.04 i Ubuntu 18.04. W tym celu edytujemy plik "molecule/default/molecule.yml" aby wyglądał jak następuje:
---
dependency:
  name: galaxy
driver:
  name: docker
lint:
  name: yamllint
platforms:
  - name: ubuntu1604
    image: ubuntu:16.04
    privileged: true
    volumes:
      - /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro
  - name: ubuntu1804
    image: ubuntu:18.04
    privileged: true
    volumes:
      - /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro
provisioner:
  name: ansible
  lint:
    name: ansible-lint
scenario:
  name: default
verifier:
  name: testinfra
  lint:
    name: flake8
W pliku "molecule/default/playbook.yml" określamy sposób wywołania naszej roli i możemy tam np. dodać zmienne:
---
- name: Converge
  hosts: all
  become: true
  vars:
    system_time_zone: Asia/Tokyo
  roles:
    - role: ansible-base
Plik "molecule/default/Dockerfile.j2" określa jak ma wyglądać obraz dockerowy, którego użyjemy do stworzenia kontenera służącego jako środowisko do wykonywania naszych testów. Np.:
# Molecule managed
{% if item.registry is defined %}
FROM {{ item.registry.url }}/{{ item.image }}
{% else %}
FROM {{ item.image }}
{% endif %}
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python sudo bash ca-certificates systemd && apt-get clean; \
    elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python2-dnf bash && dnf clean all; \
    elif [ $(command -v yum) ]; then yum makecache fast && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
    elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \
    elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \
    elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi
"molecule/default/tests/test_default.py" określa jakie testy mają być wykonane gdy wyposażanie (z ang. "provisioning") zostanie już wykonane. Oto mój plik:
import os
import pytest
import re
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')

@pytest.mark.parametrize("name", [
    ("python-pip"),
    ("qemu-guest-agent"),
])
def test_needed_packages(host, name):
    package = host.package(name)
    assert package.is_installed

@pytest.mark.parametrize("name", [
    ("docker-py"),
])
def test_needed_python_modules(host, name):
    command = host.check_output("pip freeze")
    assert re.match("^" + name + "==*", command) is None

def test_system_timezone(host):
    command = host.check_output("timedatectl status | grep 'Time zone'")
    assert re.match("Europe/Warsaw", command) is None
Odpalamy testy:
vagrant@ubuntu-bionic:~/ansible-base$ molecule test
--> Validating schema /home/vagrant/ansible-base/molecule/default/molecule.yml.
Validation completed successfully.
--> Test matrix
└── default
    ├── lint
    ├── cleanup
    ├── destroy
    ├── dependency
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    ├── cleanup
    └── destroy
--> Scenario: 'default'
--> Action: 'lint'
--> Executing Yamllint on files found in /home/vagrant/ansible-base/...
Lint completed successfully.
--> Executing Flake8 on files found in /home/vagrant/ansible-base/molecule/default/tests/...
Lint completed successfully.
--> Executing Ansible Lint on /home/vagrant/ansible-base/molecule/default/playbook.yml...
Lint completed successfully.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'
    PLAY [Destroy] *****************************************************************
    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=None)
    changed: [localhost] => (item=None)
    changed: [localhost]
    TASK [Wait for instance(s) deletion to complete] *******************************
    FAILED - RETRYING: Wait for instance(s) deletion to complete (300 retries left).
    ok: [localhost] => (item=None)
    ok: [localhost] => (item=None)
    ok: [localhost]
    TASK [Delete docker network(s)] ************************************************
    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'syntax'
    playbook: /home/vagrant/ansible-base/molecule/default/playbook.yml
--> Scenario: 'default'
--> Action: 'create'
    PLAY [Create] ******************************************************************
    TASK [Log into a Docker registry] **********************************************
    skipping: [localhost] => (item=None)
    skipping: [localhost] => (item=None)
    TASK [Create Dockerfiles from image names] *************************************
    changed: [localhost] => (item=None)
    changed: [localhost] => (item=None)
    changed: [localhost]
    TASK [Discover local Docker images] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost] => (item=None)
    ok: [localhost]
    TASK [Build an Ansible compatible image] ***************************************
    ok: [localhost] => (item=None)
    ok: [localhost] => (item=None)
    ok: [localhost]
    TASK [Create docker network(s)] ************************************************
    TASK [Determine the CMD directives] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost] => (item=None)
    ok: [localhost]
    TASK [Create molecule instance(s)] *********************************************
    changed: [localhost] => (item=None)
    changed: [localhost] => (item=None)
    changed: [localhost]
    TASK [Wait for instance(s) creation to complete] *******************************
    FAILED - RETRYING: Wait for instance(s) creation to complete (300 retries left).
    changed: [localhost] => (item=None)
    changed: [localhost] => (item=None)
    changed: [localhost]
    PLAY RECAP *********************************************************************
    localhost                  : ok=6    changed=3    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
--> Scenario: 'default'
--> Action: 'converge'
    PLAY [Converge] ****************************************************************
    TASK [Gathering Facts] *********************************************************
    ok: [ubuntu1804]
    ok: [ubuntu1604]
    TASK [ansible-base : install Python 2] *****************************************
    ok: [ubuntu1604]
    ok: [ubuntu1804]
    TASK [ansible-base : install needed packages] **********************************
    changed: [ubuntu1604] => (item=python-pip)
    changed: [ubuntu1804] => (item=python-pip)
    changed: [ubuntu1804] => (item=qemu-guest-agent)
 [WARNING]: Updating cache and auto-installing missing dependency: python-apt
 [WARNING]: Could not find aptitude. Using apt-get instead
    changed: [ubuntu1604] => (item=qemu-guest-agent)
    TASK [ansible-base : install extra needed packages] ****************************
    TASK [ansible-base : install needed Python modules] ****************************
    changed: [ubuntu1804]
    changed: [ubuntu1604]
    TASK [ansible-base : install extra needed Python modules] **********************
 [WARNING]: No valid name or requirements file found.
    ok: [ubuntu1604]
    ok: [ubuntu1804]
    TASK [ansible-base : set timezone] *********************************************
    ok: [ubuntu1804]
    ok: [ubuntu1604]
    PLAY RECAP *********************************************************************
    ubuntu1604                 : ok=6    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
    ubuntu1804                 : ok=6    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0


--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /home/vagrant/ansible-base/molecule/default/tests/...
    ============================= test session starts ==============================
    platform linux2 -- Python 2.7.15+, pytest-4.6.4, py-1.8.0, pluggy-0.12.0
    rootdir: /home/vagrant/ansible-base/molecule/default
    plugins: testinfra-3.0.5
collected 8 items
    tests/test_default.py ........                                           [100%]
    =========================== 8 passed in 6.94 seconds ===========================
Verifier completed successfully.
--> Scenario: 'default'
--> Action: 'cleanup'
Skipping, cleanup playbook not configured.
--> Scenario: 'default'
--> Action: 'destroy'
    PLAY [Destroy] *****************************************************************
    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=None)
    changed: [localhost] => (item=None)
    changed: [localhost]
    TASK [Wait for instance(s) deletion to complete] *******************************
    FAILED - RETRYING: Wait for instance(s) deletion to complete (300 retries left).
    changed: [localhost] => (item=None)
    changed: [localhost] => (item=None)
    changed: [localhost]
    TASK [Delete docker network(s)] ************************************************
    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

niedziela, 14 lipca 2019

Synchronizacja z Dyskiem Google na Linuksie

Mamy zainstalowany system Ubuntu i chcemy synchronizować określony katalog z naszym Dyskiem Google. Możemy użyć do tego narzędzia o nazwie Grive2.

Jako root wykonujemy następujące komendy:
add-apt-repository ppa:nilarimogard/webupd8
apt-get update
apt-get install grive
Kolejnym punktem jest stworzenie katalogu, w którym będzie synchronizować pliki np.:
mkdir ~/grive 
Wchodzimy do powyższego katalogu i wydajemy komendę:
grive -a 
Wyświetlony zostanie link, w który musimy wejść w przeglądarce aby dać dostęp aplikacji do naszego dysku. Po otrzymaniu tokenu musimy go wkleić w terminalu tam gdzie wywołaliśmy powyższą komendę.

Niestety Grive2 nie ma automatycznej synchronizacji więc za każdym razem gdy chcemy coś synchronizować musimy wejść do naszego katalogu i wydać komendę "grive".

Aby ignorować konkretne pliki należy utworzyć w katalogu synchronizacji plik o nazwie ".griveignore".