Next up, I'm going to show you how to create directory structures and use variables inside of our task files, inside the YAML files. So off-camera I created a new role called project, and what I want to do is, in the playbook site.yml, I want to add the project role to all hosts. So I'll go in and create a new entry under the roles key and add project, and this role's primary responsibility is going to be to create the directory structure for our application on all the different hosts in the project.
So over in the role, there's a main.yml file in tasks, and the only task that it's going to include-- or the only subtask that's going to include is config.yml, and that's where we'll put all this stuff for this task.
We're going to use a new module called file, and the file module, as you may have guessed, just allows us to create files or directories or symlinks on the remote machines, and we can specify their state as either a directory or a file or absent. And you can look up the different states for files on the command documentation or the module documentation for file.
So in this case, we want the state to be a directory, and next up we'll provide it a path, so this'll be the path to the directory that we want to make sure exists. In this case, let's name the directory emind after Evented Mind, and that'll be-- or whatever your application name is. Then we can provide an owner for the application and a group for the application. And the owner for the application, in this case, I'll say is the Ubuntu user or the SSH user for Vagrant, which is just vagrant, and the group might be something like admin, adm.
Finally, we can provide a mode for the directory, which will be the permission bits for the directory. So I'll use 2 to set the group ID automatically when we create new files in the folder, and then we'll make it executable so that the owner and the group can change into the directory and everyone else we'll hide this completely from.
Before we go any further, let's run the playbook and make sure it's still working. I'll use the tag filter so that we only run the project config task of all the various tasks that are part of this playbook. And unfortunately, it looks like it fails, and if you get a message like this where it's red, you'll see it's all red and it'll say Failed=1 for the roles that it failed on. In this case, it failed on all the different hosts that we have.
And then we have to look into the message here to see what happened. It says there was an issue creating srv/emind as requested, and it looks like the error is permission denied in this path. And the reason for that is usually because we're trying to create a directory in a place where we don't have permissions to create it, and if we're running as the Vagrant user, the Vagrant user doesn't have permissions to write to root directories. So we need to use sudo privileges to create directories under the root, and we can go back to the task and fix this by saying become true just like we did with the previous tasks. And if we run this again, this time it should succeed.
Great. It looks like all the hosts were changed as expected. And just to double-check and make sure, why don't we use ssh or vagrant ssh to go into one of those machines. I'll just pick one at random, app1.local, and make sure that the directory is the way that we expect it to look. So I'll change into the srv directory and take a look.
And look, there, we have the folder emind and its owner is vagrant, and the group is adm, and it looks like the permissions were set properly. So rwx, rws, this is the set group ID sticky bit or permission bit so that when we create new files and folders in here, the group will be set to adm automatically regardless of whoever creates it. So it looks like this is working properly and we have our folder emind.
But let's go make the YAML file a little bit more sophisticated. I'd like to create multiple directories, not just this one. I'm going to create a few subdirectories inside of this and I could create multiple tasks to do that, but a slightly cleaner way would be to use a variable. I can use a special key called with_items and that's going to take an array of items like this, item1, item2, it could be strings, it could be objects, whatever we want the items to be, and that's a way of creating a loop. So automatically it will go through each one of these items and call the module one time for each item.
Then we can refer to the individual items using a variable reference that looks like this. If we were trying to access a specific property and the item, if it was an object, we could say dot and whatever the item prop was.
The syntax that's being used here is Jinja2 syntax. So this is actually passing through a templating engine and the templating engine allows us to use special syntax to interpolate variables and other types of functions right into our YAML file.
Again, this particular templating engine is called Jinja2. It's a Python templating engine and it has a lot of functionality and features in it. And you can come over to the Ansible documentation and take a look at some of the things that you can do. And it also has its own homepage and its own documentation, so you can check out that at its website.
Let's fill in these directory structures. The first one is going to be the one that we just created. So srv/emind and then underneath that, let's create a directory for the current source code to the current release. This is going to be the application that's actually running on the machine if we are running it as an app. And I'll create a folder for logs, I'll create a folder for temporary stuff there we're going to delete, and I'll create a folder for configuration. So these will be settings, like JSON settings or something like that, that our app might use.
One thing to be mindful of is if the value is a variable like this, we need to put it in quotes. If it's part of the end of a string, like if it was over here, it'd be fine, but if it's the beginning of the value, we have to quote it. And if you don't do that, you'll get a syntax error when you try to run the playbook. So we'll just go ahead and put this in quotes and you can see I already got that error off-camera.
So let's go ahead and run this again, and this time around it should create all of those folders and it should do it under the same task label. You can see inside of each role all the changes that were made. So all those new folders were created. And we can just go check it out again, just to make sure, by ssh-ing into one of the machines. I'll pick db1.local this time, and we'll change into the srv/emind application, and there's our new folders that we created with the correct owners.
It would be nice if we could extract those values for the folder names, for example, and the name of the application, and the database name, and all these different things into variables that could be reused throughout our tasks, and you can do that using the variables feature of Ansible. This page here on the documentation will show you all the different possible ways you can store variables, but I'm going to show you the way that I do it on Evented Mind.
Inside the environments folder underneath the environment that we we're using, create another folder called group_bars. And then underneath that we can create a folder for each of the roles that we want to have a variable specific to. But it makes it easier, if the project is simple, just to have them all in one place, and so I'm going to create a folder called all, and inside of that folder we'll create another one called all.yml. So a little bit convoluted, but this is going to be the structure that automatically gets recognized by Ansible. So we've created a folder called [? group_vars, ?] and then another for the roles that we want to apply these to, and then the actual YAML file, which will contain key value pairs of our variables.
We can use regular YAML syntax to create these variables, and the first one I've created is called env. It's short for environment. And in this case, the environment is going to be local instead of staging or production or something like that. The next one I would create is app, and then just give it the name of the application. In this case, emind.
And then previously, we had a path for the application hard-coded into the project role, but now I'm going to create a variable called app_path. And we'll set that equal to srv, and then we can use the variable app inside of this. So notice I don't need to use a string syntax here, because the beginning of this value does not start with a variable.
Then we can go back to the project role and use these variables there. So inside of config.yml, instead of having this hard-coded like this, I'll use the app_path variable. And now I can easily change the app path in one place, in the variables file, and it'll be reflected in all of the places that that variable's used. Note again that because this value starts with a variable value, I need to quote it, and so I just make the entire thing quoted. It makes it easier to read.
Let's go try this out one more time before we move on to make sure it's still working. Great. The playbook runs as expected, except nothing has changed, because nothing changed with the folders and they were already created the last time, but this shows that the variables are working as intended.