The Wizard: Ansible, Molecule and Test Driven Development

Magic has existed since the dawn of time. It has always been there, hidden in plain sight, making amazing things possible for those willing to open their eyes and harness its power.

You can't remember the first time you used it, and yet it feels like you've never existed without it. You've obliterated endless armies of enemies by virtue of spells and enchantments, and you've also constructed awe-inspiring marvels from the ground up. As a result, you're renowned for your overwhelming powers in every single one of your incarnations through time. Everyone knows about the Wizard.

This affinity with magic grants you the ability to see the real nature of things. Time for example, is not linear (as it was believed to be up until the late 21st century). Sometimes, it is hard to keep up with it...

Suddenly you wake up. Or you come into consciousness. What year is it?

  • So yeah, the output is still red, I really don’t know what else to do.
  • What?

You look to your left. N is sitting right there. He looks at you with mild concern in his eyes.

  • Are you okay?
  • Yeah, sorry.
  • As I was saying, I don’t know why this isn’t working.

You look at the spell he is trying to cast. His intention is clear: he wants to share the work of his clan with all the sentient entities in this reality by means of a portal.

---

# task file for nginx

 

- name: Install nginx

  yum:

    name: nginx

    state: present

It is clear why he is failing. He is not taking into account the constraints of his clan’s environment. He must first gather the requirements of his spell, and he’s not looking at the right places. As a matter of fact, he is not looking at all. The answer is simple. Nevertheless...

You reflect for a second, wondering if someone even taught N how to properly cast a spell.

  • How are you testing your role?
  • Hum…
  • Are you testing your role?
  • Yes, of course.
  • How?
  • I run my code on the target machine, and then I hope it doesn’t fail.

You feel betrayed by the sound of his words. This is unacceptable. A whole stack of death spells appear in your head. Breathe, you repeat to yourself. In. Out. You keep going.

  • Are you aware of the concept of Test-Driven Development?
  • Yeah, I used to work that way all the time when I was doing dev with the team.
  • So you’re familiar with it.
  • Sure, L taught me.

Ah, a familiar name. You like L. You fought together at Desaix, before he started mentoring his own apprentices on the arts of war. He must have taught N a thing or two. You wonder if he will be capable of surviving the trial.

  • Why don’t you test your code that way instead?
  • Because it is not Java, it’s Ansible.
  • So?
  • It’s not the same.
  • Let’s start over. Do you mind if I grab the keyboard?
  • Not at all.

You harness your energy as you get ready to cast your first spell. You feel alive again. You’re in familiar grounds. Before you start working, you need an empty canvas, a playground to construct the structure and robustness of your magic. An isolated enclosure which will allow you to try and cast your spells, and which also follows the same rules as the target reality for the spell you’re trying to create.

  • Have you ever heard of Molecule?
  • ...hum?
  • It’s a Python tool. It allows you to test your Ansible code on many different levels, using local infrastructure.
  • Local? Like a Virtual Machine?

He’s fast. Time to make a choice. The Wanderer, or the Whale? The Whale is indeed faster.

  • Not necessarily. You can use different providers. Vagrant and Docker are the most common if you want to work locally. We’re going to use Docker, since containers have a faster start-up time.
  • Right on, Docker!

The Whale it is. You concentrate and cast your first incantation.

$ molecule init role --role-name nginx --driver-name docker

You feel the energy flowing through. A whirlpool of blue water forms in the sky, and The Whale appears from inside of it. It comes swimming down through the air, looking at you both, and sings its song loudly. An empty enclosure materializes out of thin air, trapping you and N inside of it. The Whale disappears from the whirlpool where it came, right before the whirlpool itself vanishes into nothingness, as fast as it had appeared.

--> Initializing new role nginx…

 

nginx/

├── README.md

├── defaults

│   └── main.yml

├── handlers

│   └── main.yml

├── meta

│   └── main.yml

├── molecule

│   └── default

│       ├── Dockerfile.j2

│       ├── INSTALL.rst

│       ├── create.yml

│       ├── destroy.yml

│       ├── molecule.yml

│       ├── playbook.yml

│       └── tests

│           └── test_default.py

├── tasks

│   └── main.yml

└── vars

    └── main.yml

You feel the need to test N. He’s looking at what you’ve produced, his eyebrows frowning.

  • What do you think? It’s the standard role structure, isn’t it? - you ask, naively
  • Yes, with the exception of the molecule directory with all of that stuff in it.

Good. He realises there’s a difference. How can you easily explain the fact that, in order to control such an enclosure, you need intermediate spells to both create it and destroy it at will? You decide that it is not important right now, you’ll come back later to it.

  • Don’t think about it yet. Let’s see if our test framework is working properly.

You concentrate and you cast the spell on the enclosure/playground.

$ molecule test

--> Test matrix

└── default

    ├── destroy

    ├── dependency

    ├── syntax

    ├── create

    ├── converge

    ├── idempotence

    ├── lint

    ├── side_effect

    ├── verify

    └── destroy

N is impressed by the fireworks. He has so many questions. You feel overjoyed, but you give him a second so that he can get his ideas together.

  • Test matrix? Default? And what are all of those things?
  • When I talked about testing Ansible code on many different levels, this is what I meant. The default scenario is what gets executed when you type `molecule test` on your terminal, and it contains all of these actions: destroy, dependency, syntax, create, converge, idempotence…
  • Yeah, I can read.

Snarky. He wants you to know he’s not a child. He contemplates the enclosure’s structure. You wait for a while until he starts talking again.

  • Are these… actions executed in order?
  • Yes, unless you say otherwise.
  • What do you mean?

You decide now is the time to go down the rabbit hole.

  • Since I typed `molecule test`, molecule executes the default test sequence, which corresponds to all of these actions. We can also redefine different sequences for different targets if the default test sequence does not satisfy our testing criteria.
  • Different sequences? Like skipping all tests? Or executing only… let’s say… the linting?
  • Sure.

You raise your hand, and you snap your fingers. You hear warping sounds as the enclosure somehow shifts. Everything looks the same, yet everything feels different.

# molecule.yml

---

dependency:

  name: galaxy

driver:

  name: docker

lint:

  name: yamllint

platforms:

  - name: instance

    image: centos:7

provisioner:

  name: ansible

  lint:

    name: ansible-lint

scenario:

  name: default

  test_sequence:

    - lint

verifier:

  name: testinfra

  lint:

    name: flake8

You take a deep breath, and you cast the spell again.

 $  molecule test

--> Test matrix

└── default

    └── lint

--> Scenario: 'default'

--> Action: 'lint'

--> Executing Yamllint on files found in /private/tmp/test/nginx/...

Lint completed successfully.

--> Executing Flake8 on files found in /private/tmp/test/nginx/molecule/default/tests/...

Lint completed successfully.

--> Executing Ansible Lint on /private/tmp/test/nginx/molecule/default/playbook.yml...

Lint completed successfully.

Once you’re finished, you look back at N. He gets where you’re going.

  • That was faster.
  • Yes.
  • I think we should add some actions back though.
  • I agree. Which ones?
  • Let me see the first test matrix we saw.

He studies the playground spells thoroughly.

  • Why is the destroy action executed first?
  • Because the whole point is to be able to test that your role works in newly created infrastructure.
  • Yeah, but it’s going to take longer if I need to create and destroy the container each time I launch my test, right?

Good. He is connecting the dots, seeing outside of the box. You decide that you like N a little better now. Enough to keep the game going.

  • Yes, but we will deal with that later. Just concentrate on the actions that we should execute in order to fully test our role.
  • Okay, so then we need `destroy`... what is `dependency`?
  • It allows you to pull dependencies from the galaxy, if you need them.
  • Ah, let’s skip that then. What’s the difference between `syntax` and `lint`?
  • The `syntax` action runs your playbook using the `--syntax-check` option native in Ansible, whereas `lint` checks your files using flake8, yamllint and ansible-lint.
  • So `create` uses the create.yml file we just saw under the molecule directory inside the role right?
  • Yes. It creates a Docker container suitable for Ansible use. And `destroy` uses the destroy.yml file. They’re both playbooks, so these actions are managed by Ansible.
  • Eat your own dog food, alright. So `converge` executes my role on the container?
  • That’s right.
  • What about `side-effect` and `verify`?
  • `side-effect` lets you produce situations in which you will be able to test more things, like HA failover, for example. I don’t think we’ll be needing that one for this situation. And `verify` is where all the magic happens. That’s where our unit tests get executed.
  • Unit tests? What?
  • You’ll see in a second.
  • Alright. So I guess we’ll need `destroy`, `syntax`, `lint`, `create`, `converge`, `verify` and `destroy` again. No, wait. No destroy at the end. That’s why it is at the beginning.
  • As you wish.

You notice something is missing, but you decide to keep going to take advantage of the synergy. You will get back to it, eventually. You snap your fingers once again, altering the enclosure to match his words.

# molecule.yml

...

scenario:

  name: default

  test_sequence:

    - destroy

    - syntax

    - lint

    - create

    - converge

    - verify

You cast the spell once again, using the modified reality of the enclosure. The fireworks appear one more time, this time slightly smaller.

 $  molecule test

--> Test matrix

└── default

    ├── destroy

    ├── syntax

    ├── lint

    ├── create

    ├── converge

    └── verify

N seems happy. He understands how it works. You take advantage of his motivation to feed him more knowledge.

  • This is going to act as our full test scenario. We’re not going to run all of that every single time we test though.
  • Hum? Why not? What are we doing instead?

Knowledge is power. Everyone knows that. If you’re able to acquire knowledge faster, you’re able to acquire power faster as well.

  • Because we want our feedback loop to be as short and quick as possible. So we’re going to `create` our resources, `converge` them with our Ansible role, and `verify` that the results are what we expect. You can do this by typing `molecule create`, `molecule converge` and `molecule verify`. Then, if it all works, we’re going to run our whole test sequence.

You let that sink in for a while, hoping that he doesn’t give up. You look outside the window. It’s raining. You like rain. At least on Earth. It’s better than on Venus, where it melts the skin off of your bones if you don’t cast a protection enchantment around you. That is if the temperature or the pressure don’t get you first. N doesn’t give up. He looks at you.

  • Okay, I think I have it. Let’s create the container.

You start summoning The Whale again, but you only hear its song this time. It is as loud as it was before. The enclosure disappears and reappears around you.

$ molecule create

--> Test matrix

└── default

    └── create

--> Scenario: 'default'

--> Action: 'create'

You look at N, and you think about the next steps. This is going to sound familiar to him.

  • Let’s run our tests. They are located on the test_default.py file, under the molecule directory.

You clap your hands once, loudly. As you separate them a stream of shiny green light emanates from them.

 $molecule verify

--> Test matrix

└── default

    └── verify

--> Scenario: 'default'

--> Action: 'verify'

--> Executing Testinfra tests found in /private/tmp/test/nginx/molecule/default/tests/...

Verifier completed successfully.

Everything is okay, for now. Back to N.

  • What are the three steps of Test-Driven Development?
  • Red, green, refactor.

L had done his job right.

  • Excellent. What are you trying to accomplish?
  • I want to expose my web app using nginx.
  • What do you need for that?
  • Well, I need to install nginx.
  • Let's go to red then.

You spawn a glob of energy with your hands and use it to craft a rule within the enclosure’s reality:

import os

 

import testinfra.utils.ansible_runner

 

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(

    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')

 

def test_nginx_is_installed(host):

    # Given

    nginx = host.package('nginx')

 

    # Then

    assert nginx.is_installed
  • This feels familiar.
  • It's because it is.

You clap your hands together, just as you did a minute ago. Nevertheless, red shiny light appears this time as you separate them.

     $ molecule verify

 

    =================================== FAILURES ===================================

    _________________ test_nginx_is_installed[ansible://instance] __________________

 

    host = <testinfra.host.Host object at 0x10a9a7350>

 

        def test_nginx_is_installed(host):

            # Given

            nginx = host.package('nginx')

 

            # Then

    >       assert nginx.is_installed

    E       assert False

    E        +  where False = <package nginx>.is_installed

 

    tests/test_default.py:14: AssertionError
  • See? That's red. We're good. What's next?
  • Green. So now we'll add the task we need in order to install the server.

You close your eyes and concentrate. The structure of the spell N had in the first place is recreated from the ground.

---

# tasks file for nginx

 

- name: Install nginx

  yum:

    name: nginx

    state: present

Back to square one. It is now the same spell N had when he asked you for help. You cast the spell, knowing what will happen beforehand.

 $molecule converge

--> Test matrix

 

└── default

    ├── create

    └── converge

--> Scenario: 'default'

--> Action: 'create'

Skipping, instances already created.

--> Scenario: 'default'

--> Action: 'converge'

 

    TASK [nginx : Install nginx] ***************************************************

    fatal: [instance]: FAILED! => {"changed": false, "failed": true, "msg": "No package matching 'nginx' found available, installed or updated", "rc": 126, "results": ["No package matching 'nginx' found available, installed or updated"]}

No fireworks. N seems discouraged. You think about giving him a little push. He deserves it. It's not about the magic itself, it's about context. He's not getting the sources for his portal from the right index. He needs the Epel codex.

  • At least it's easier to test now.
  • Don't be sad. The nginx package is contained within the epel-release repository.
  • What? You could have told me that from the beginning, couldn't you?
  • We wouldn't be talking about infrastructure testing if I had, would we?

He smiles. He's getting in the game, getting the feel of the mindset. He likes it.

  • Let's try it again.

You modify the structure of the spell once again, specifying that you want your portal to be created following the instructions on the Epel codex.

---

# tasks file for nginx

 

- name: Install epel release

  yum:

    name: epel-release

    state: present

 

- name: Install nginx

  yum:

    name: nginx

    state: present

You cast the spell, with the new structure.

$ molecule converge

--> Test matrix

 

└── default

    ├── create

    └── converge

--> Scenario: 'default'

--> Action: 'create'

Skipping, instances already created.

--> Scenario: 'default'

--> Action: 'converge'

…

    PLAY RECAP *********************************************************************

    instance                   : ok=3    changed=2    unreachable=0    failed=0

The spell works, or at least it seems to work. The fireworks make the air vibrate within the enclosure. N seems happy. He doesn't have the right reflexes yet. Can you blame him? It's his first existence, and he's just starting to play with magic. He won't realize that his portal is not yet active, he’s just dazzled with the fireworks. It’s time for a new trial.

  • Excellent! I guess we're done!
  • Yeah. Did you try it?
  • Oh right, let's see. Can I login to the container somehow?
  • Yeah, `molecule login`.
[root@instance /]# curl localhost

curl: (7) Failed to connect to ::1: Cannot assign requested address
  • Hm. But... ah, right! I never started the service.
  • Be my guest, add the missing code needed to do it.

He is about to modify the spell’s structure, when he suddenly stops. He looks at you.

  • No. It's not the right way. Can't I test that first?

He passed the test. He might make a good disciple. You start to understand why L started teaching in the first place.

  • Sure, just add another test.
  • Alright…
def test_nginx_is_running(host):

    # Given

    nginx = host.service('nginx')

 

    # Then

    assert nginx.is_running
  • I love writing tests in Python by the way. It’s way simpler than in Java.
  • Don’t let L hear you say that. Try it.

You know the Whale won’t let N get away with that, but you want to see him try. Failure is part of the learning process. It is almost more important than success. It’s the only way he can learn to get up and keep trying.

$ molecule verify

    E       AssertionError: Unexpected exit code 1 for CommandResult(command=u'systemctl is-active nginx', exit_status=1, stdout=u'', stderr=u'Failed to get D-Bus connection: Operation not permitted')

N claps his hands like you did before, but no light appears as he separated his hands. He looks as you, confused.

  • Hm, this doesn’t look like the good kind of red. It is not an assertion error.
  • It is not. Testinfra (your Python unit testing library) uses Systemd, Upstart or SysV in order to test if the services are running. Docker containers don’t have an init system. They do have Tini, but it doesn’t work the same way.
  • So I need to change the image that I’m using. Is there a Centos image that includes Systemd?
  • Yeah, it’s called centos/systemd.
  • Go figure. Where can I modify that?
  • On the molecule.yml file. You will need to specify the container’s command instruction in order to start Systemd, because Molecule overrides it by default. You will also need a privileged container, since Systemd requires CAP_SYS_ADMIN and non-privileged containers do not have that capability.
  • Hum, I didn’t quite get all of that, but I think I’ll ask you about it again tomorrow. Let me redo the configuration.
platforms:

  - name: instance

    image: centos/systemd

    privileged: True

    command: /usr/sbin/init

The Whale will surely accept this offering. You know it will not reject your summoning anymore. N verifies the spell again.

      $ molecule test

    =================================== FAILURES ===================================

    ____________ test_nginx_is_running_and_enabled[ansible://instance] _____________

 

    host = <testinfra.host.Host object at 0x110adff50>

 

        def test_nginx_is_running(host):

            # Given

            nginx = host.service('nginx')

 

            # Then

    >       assert nginx.is_running

    E       assert False

    E        +  where False = <service nginx>.is_running

 

    tests/test_default.py:20: AssertionError

 

    -- Docs: http://doc.pytest.org/en/latest/warnings.html

    ================ 1 failed, 1 passed, 1 warnings in 6.74 seconds ================
  • Done, we're back to red. Now I'll make the changes in order to start the service.

You smile as you see N craft. He is not ready yet, but he is well under way. You feel proud of him.

  • Alright, I’m done. What do you think?

He shows you the structure of his spell.

---

# tasks file for nginx

 

- name: Install epel release

  yum:

    name: epel-release

    state: present

 

- name: Install nginx

  yum:

    name: nginx

    state: present

 

- name: Start nginx

  command: systemctl start nginx

All of your previously found pride turns into disappointment. This is preposterous. It can’t be serious. He can't be serious. He should understand that you can't be spawning portals indefinitely. The results could be catastrophic.

But then…you contemplate him. You realize he's tired. Idempotence is a hard concept to grasp too. Benjamin Peirce taught it for over 50 years at Harvard. You remember the first time Merlin brought you to an enclosure like this one. Just the memory of it makes you feel goosebumps on the back of your neck.

You decide that you’re going to let him continue, for education purposes only. He’ll realize the problem with his proposal.

  • Why don’t you try it?

He casts the spell himself.

$ molecule converge

--> Test matrix

└── default

    ├── create

    └── converge

--> Scenario: 'default'

--> Action: 'create'

Skipping, instances already created.

--> Scenario: 'default'

--> Action: 'converge'

 

    PLAY RECAP *********************************************************************

    instance                   : ok=4    changed=1    unreachable=0    failed=0

Right after the fireworks, a green portal appears right in front of both of you. It seems to work.

  • Great. I’ll verify now.
 $ molecule verify

--> Test matrix

 

└── default

    └── verify

--> Scenario: 'default'

--> Action: 'verify'

--> Executing Testinfra tests found in /Users/sebiwi/stuff/test/nginx/molecule/default/tests/...

    ============================= test session starts ==============================

    ===================== 2 passed, 1 warnings in 6.70 seconds =====================

Verifier completed successfully.

Green light everywhere.

  • Perfect! Green!

Or is it? You decide to give him a little push in the right direction.

  • Try running the whole test suite.
  • Alright
 $molecule test

--> Test matrix

└── default

    ├── destroy

    ├── syntax

    ├── lint

    ├── create

    ├── converge

    └── verify

 

---> Scenario: 'default'

--> Action: 'lint'

--> Executing Yamllint on files found in /Users/sebiwi/stuff/test/nginx/...

Lint completed successfully.

--> Executing Flake8 on files found in /Users/sebiwi/stuff/test/nginx/molecule/default/tests/...

Lint completed successfully.

--> Executing Ansible Lint on /Users/sebiwi/stuff/test/nginx/molecule/default/playbook.yml...

    [ANSIBLE0012] Commands should not change things if nothing needs doing

    /Users/sebiwi/stuff/test/nginx/tasks/main.yml:14

    Task/Handler: Start nginx

No fireworks, no lights.

  • What? Oh, that’s just ansible-lint, it often fails for that sort of thing, I’ll just deactivate it for the task.

You stare in disbelief as N “corrects” the structure of the spell.

---

# tasks file for nginx

 

- name: Install epel release

  yum:

    name: epel-release

    state: present

 

- name: Install nginx

  yum:

    name: nginx

    state: present

 

- name: Start nginx

  command: systemctl start nginx

  tags:

    - skip_ansible_lint

He finishes the modifications and he casts the spell once again.

 $  molecule test

--> Test matrix

 

└── default

    ├── destroy

    ├── syntax

    ├── lint

    ├── create

    ├── converge

    └── verify

 

All steps completed successfully.

He looks at you proudly. You’re dismayed. He’s being naive and reckless. You realize that it is time to handle the situation.

  • We’re green now. I think we’re finished.
  • Not really. Previously, ansible-lint said that you’re changing things even if nothing needs to be done.
  • Yeah, it’s because I was starting a service.
  • Yes, but since you’re not using an Ansible module, you’re executing an action each time you run your playbook.
  • I thought Ansible was idempotent.
  • Not really. You can make idempotent code with Ansible. It doesn’t mean everything you do with it is going to be idempotent. Some things are not meant to be idempotent.
  • Like what?
  • Application deployments, for example. You’re deploying a new version of the application. It is meant to change things.
  • I see. But this role should be idempotent.
  • Certainly. You can add a new action to your default scenario test suite in order to test for idempotence.

You close your eyes as you change the reality of the enclosure around you once again.

scenario:

  name: default

  test_sequence:

    - destroy

    - syntax

    - lint

    - create

    - converge

    - idempotence

    - verify

You cast the spell again.

$  molecule test

--> Test matrix

 

└── default

    ├── destroy

    ├── syntax

    ├── lint

    ├── create

    ├── converge

    ├── idempotence

    └── verify

 

--> Scenario: 'default'

--> Action: 'idempotence'

ERROR: Idempotence test failed because of the following tasks:

* [instance] => nginx : Start nginx

N looks at you.

  • So we weren’t actually at green yet.
  • We weren’t. Idempotence is a behaviour of our code. It also needs testing.
  • I can fix it though.
  • Show me.

You hand over the control of the enclosure to N. He modifies the spell structure.

---

# tasks file for nginx

 

- name: Install epel release

  yum:

    name: epel-release

    state: present

 

- name: Install nginx

  yum:

    name: nginx

    state: present

 

- name: Start nginx

  service:

    name: nginx

    state: started

You smile with relief.

  • Much better. Try it.
  • Right away.
 $  molecule test

--> Test matrix

 

└── default

    ├── destroy

    ├── syntax

    ├── lint

    ├── create

    ├── converge

    ├── idempotence

    └── verify

 

All steps completed successfully.
  • And now we’re green.
  • Yes!
  • Let’s continue with our TDD approach. Red, Green, and... ?
  • Refactor! Do you see any possible improvements on this code?
  • Well, you could use your “Start nginx” task as a handler, which is triggered by the installation of nginx, couldn’t you?
  • Yes, I could.
---

# tasks file for nginx

 

- name: Install epel release

  yum:

    name: epel-release

    state: present

 

- name: Install nginx

  yum:

    name: nginx

    state: present

  notify: Start nginx

---

# handlers file for nginx

 

- name: Start nginx

  service:

    name: nginx

    state: started

 $  molecule test

--> Test matrix

 

└── default

    ├── destroy

    ├── syntax

    ├── lint

    ├── create

    ├── converge

    ├── idempotence

    └── verify

 

All steps completed successfully.

N looks euphoric. He’s applying his well-earned war experience to a whole different situation, and it is working like a charm. There’s a slight catch within your proposal, but you decide that you will let him discover it by himself.

  • Wow, that worked out great! What else?
  • You could have only one “yum” task and iterate over the packages that you want to install.
  • Good call.
---

# tasks file for nginx

 

- name: Install epel-release and nginx

  yum:

    name: "{{ item }}"

    state: present

  with_items:

    - epel-release

    - nginx

  notify:

    - Start nginx

$ molecule test

...

--> Scenario: 'default'

--> Action: 'converge'

 

    PLAY [Converge] ****************************************************************

 

    TASK [Gathering Facts] *********************************************************

    ok: [instance]

 

    TASK [nginx : Install epel-release and nginx] **********************************

    failed: [instance] (item=[u'epel-release', u'nginx']) => {"changed": false, "failed": true, "item": ["epel-release", "nginx"], "msg": "No package matching 'nginx' found available, installed or updated", "rc": 126, "results": ["No package matching 'nginx' found available, installed or updated"]}

 

    PLAY RECAP *********************************************************************

    instance                   : ok=1    changed=0    unreachable=0    failed=1
  • Hm. I guess I need to install epel-release before I can install nginx. It doesn’t work if I do both at the same time.
  • True.
  • I wouldn’t had realized that without the tests. I would have done my refactoring, and it would have worked, since epel-release would already be installed on my machine when trying to install it along with nginx in a single task.
  • Indeed. Also, with the current code structure, you’re not only starting nginx after installing nginx, but also after installing epel-release.
  • Right. I shouldn’t. That was a test, wasn’t it?
  • Maybe.

N transforms the spell structure back to its previous form. You feel relaxed. You always feel this way after creating something impressive. You feel the same way you felt when you structured the pyramids, only to a minor extent. This time, however, the actual creator is N. You only acted as a catalyzer. The knowledge was always inside of N, he just needed to hear the right questions.

N looks tired, but he wants to keep going.

  • We just went through a full Red/Green/Refactor cycle. Sweet!
  • Do you want to do another one?
  • Absolutely!
  • What happens if your VM gets rebooted?
  • Nothing, nginx will still be installed.
  • Will it be running?
  • Oh, no. It won’t. Yikes.
  • You should enable it then. You know what to do.

N creates a new rule within the enclosure’s reality.

def test_nginx_is_enabled(host):

    # Given

    nginx = host.service('nginx')

 

    # Then

    assert nginx.is_enabled

He claps his hands together. Red light appears everywhere.

$ molecule verify

--> Test matrix

└── default

    └── verify

--> Scenario: 'default'

--> Action: 'verify'

--> Executing Testinfra tests found in /Users/sebiwi/stuff/test/nginx/molecule/default/tests/…

 

    =================================== FAILURES ===================================

    __________________ test_nginx_is_enabled[ansible://instance] ___________________

 

    host = <testinfra.host.Host object at 0x103b20fd0>

 

       def test_nginx_is_enabled(host):
            # Given
            nginx = host.service('nginx')

            # Then
    >       assert nginx.is_enabled
    E       assert False
    E        +  where False = <service nginx>.is_enabled

    tests/test_default.py:28: AssertionError
    -- Docs: http://doc.pytest.org/en/latest/warnings.html
    ================ 1 failed, 2 passed, 1 warnings in 7.29 seconds ================
  • Red.
  • Go for Green.

N modifies the structure of the spell to match the new reality constraints. He’s got a clear head now, he knows what is missing.

---

# handlers file for nginx

 

- name: Start nginx

  service:

    name: nginx

    state: started

    enabled: yes

 $  molecule test

--> Test matrix

 

└── default

    ├── destroy

    ├── syntax

    ├── lint

    ├── create

    ├── converge

    ├── idempotence

    └── verify

 

All steps completed successfully.

The green portal appears in front of you both. You know it is sustainable, you both tested it thoroughly. It’s a proper wizard spell. People would say it is one of your own. N looks at you and smiles.

  • Green.
  • Yes. Can you refactor?
  • I don’t think so. It looks pretty clean to me.
  • I agree.

You do agree. N seems in charge. He needs more time to develop himself, to learn, to become comfortable with this whole new world. You won’t be as useful during that period as you were now. It’s time to go.

  • You know how to continue, right?
  • Yes, I think I can manage. This is pretty cool. Thanks.
  • No problem kid. Just call me if you need more help.
  • Will do. Hey, do you..?

N looks at where you used to be 10 seconds ago. You are no longer there.