Run Oracle Linux Tasks with Oracle Linux Automation Engine
Introduction
Oracle Linux Automation Engine enables administrators to automate the initial setup of Oracle Linux and run other administrative jobs using an Infrastructure as code (IaC) configuration management tool through a series of playbooks and tasks.
Objectives
In this tutorial, you'll learn how to:
- Write playbooks that:
- Create a user
- Adds the user to the sudo group
- Copies a local SSH public key to the user's authorized_keys file
- Adds a DNF repository and installs a package
- Manipulates files
Prerequisites
A minimum of two Oracle Linux systems with the following configuration:
- a non-root user with sudo permissions
- ssh keypair for the non-root user
- the ability to SSH from one host to another using a passwordless SSH login
Deploy Oracle Linux Automation Engine
Note: If running in your own tenancy, read the linux-virt-labs
GitHub project README.md and complete the prerequisites before deploying the lab environment.
Open a terminal on the Luna Desktop.
Clone the
linux-virt-labs
GitHub project.git clone https://github.com/oracle-devrel/linux-virt-labs.git
Change into the working directory.
cd linux-virt-labs/olam
Install the required collections.
ansible-galaxy collection install -r requirements.yml
Update the Oracle Linux instance configuration.
cat << EOF | tee instances.yml > /dev/null compute_instances: 1: instance_name: "ol-control-node" type: "control" 2: instance_name: "ol-host" type: "remote" use_olae_only: true EOF
Deploy the lab environment.
ansible-playbook create_instance.yml -e localhost_python_interpreter="/usr/bin/python3.6" -e "@instances.yml"
The free lab environment requires the extra variable
local_python_interpreter
, which setsansible_python_interpreter
for plays running on localhost. This variable is needed because the environment installs the RPM package for the Oracle Cloud Infrastructure SDK for Python, located under the python3.6 modules.The default deployment shape uses the AMD CPU and Oracle Linux 8. To use an Intel CPU or Oracle Linux 9, add
-e instance_shape="VM.Standard3.Flex"
or-e os_version="9"
to the deployment command.Important: Wait for the playbook to run successfully and reach the pause task. At this stage of the playbook, the installation of Oracle Linux is complete, and the instances are ready. Take note of the previous play, which prints the public and private IP addresses of the nodes it deploys and any other deployment information needed while running the lab.
Write the Initial Setup Playbook
Many playbooks leverage variables and variable files containing key-value pairs, allowing the actual playbook tasks to be dynamic while the code remains static. A playbook includes the variables during runtime, where its values become part of the plays when running tasks.
Oracle Linux Automation Engine allows defining these variables in several locations, each having an order of precedence. Playbook-level variables are defined within the playbook using the vars or vars_files directive. The vars directive specifies the variables as part of the play, while the vars_files directive includes an external file containing the variables. Developers can create these variables dynamically or statically from another play, as we'll do in this example, which defines the system configuration before running the playbook.
Open a new terminal and connect via SSH to the ol-control-node system.
ssh oracle@<ip_address_of_instance>
Verify that the Oracle Linux Automation Engine command is available.
ansible --version
Create a working project directory.
mkdir -p ~/ol-playbook
Create a variable directory and file for the project.
mkdir ~/ol-playbook/vars
touch ~/ol-playbook/vars/defaults.yml
Add the variables and values to the file.
cat << EOF | tee ~/ol-playbook/vars/defaults.yml > /dev/null --- username: oracle user_default_password: oracle ssh_key_file: id_rsa ssh_private_key_file: "{{ lookup('file', lookup('env','HOME') + '/.ssh/' + ssh_key_file + '.pub') }}" additional_packages: ['git'] EOF
This information explains each variable and how we'll use it:
username
: The name of the sudo user created when running the playbook. For this example, the user's name will beoracle
.user_default_password
: The default password for theoracle
user when created. The password is required when runningsudo
commands.ssh_key_file
: Sets the name of the user's SSH key pair.ssh_private_key_file
: Copies the user's SSH public key to the remote user's authorized_key file at the given path. The example uses the lookup plugin to find the public keyid_rsa.pub
in the local users$HOME/.ssh/
directory.additional_packages
: Add the name of any additional packages to install in array format. Each package in the array should be enclosed in single quotes and separated by a comma. If installing an appstream module such ascontainer-tools
, the array would look like['git',' @container-tools:ol8']
.
Create the playbook file.
cat << EOF | tee ~/ol-playbook/setup.yml > /dev/null --- - hosts: all become: yes vars_files: - vars/defaults.yml tasks: - name: Generate new ssh keypair community.crypto.openssh_keypair: path: "~/.ssh/{{ ssh_key_file }}" size: 2048 comment: olam ssh keypair become: true become_user: "{{ username }}" delegate_to: localhost - name: Add a user account with access to sudo ansible.builtin.user: name: "{{ username }}" password: "{{ user_default_password | password_hash('sha512') }}" comment: Default Oracle user groups: wheel append: yes update_password: on_create - name: Set the authorized key for the user using a local public key file ansible.posix.authorized_key: user: "{{ username }}" state: present key: "{{ ssh_private_key_file }}" - name: install additional packages ansible.builtin.dnf: name: "{{ additional_packages }}" state: latest EOF
A playbook's specific tasks and module names aim to make the playbook self-documenting. These items instruct where and who runs the plays:
hosts: all
: This line specifies which hosts from the inventory will run the tasks.become: yes
: Instructs the tasks within this section to run with thesudo
privilege by default.vars_files
" This directive loads the variables file containing this tutorial's playbook configuration.
Install the Required Collections
The ansible-core
package contains a minimal set of modules for managing hosts called the ansible.builtin
collection. A collection is a method for distributing playbooks, roles, modules, or plugins that perform a targeted task. ansible-core
requires downloading and installing any modules or collections required outside the builtins.
As the playbook above uses the ansible.posix
collection, we need to install this collection and others. The easiest way to do this is by creating a requirements file that contains all dependencies.
Create a requirements file.
cat << 'EOF' | tee ~/ol-playbook/requirements.yml > /dev/null --- collections: - name: ansible.posix - name: community.crypto EOF
Install the collection.
ansible-galaxy collection install -r ~/ol-playbook/requirements.yml
The output shows the process of retrieving the compressed archive file from the Galaxy site and then installing it into your home directory under
.ansible/collections.
Note: If the output shows
ERROR: Ansible requires the locale encoding to be UTF-8; Detected None.
, this indicates an incorrect locale setting foransible
. Fix the issue by setting these two environment variables:export LC_ALL="en_US.UTF-8" export LC_CTYPE="en_US.UTF-8"
Run the Playbook
Before running the playbook, we must create an inventory file for this project pointing to the remote Oracle Linux instance we plan to manage.
Assign the IP address of the ol-host to a variable for our remote system.
This variable makes it easier to script the creation of the inventory file.
export REMOTE=<ip_address_of_instance>
Create a new inventory file project directory.
cat << EOF | tee ~/ol-playbook/inventory > /dev/null [production] ol-host ansible_host=$REMOTE EOF
Change to the project working directory.
cd ~/ol-playbook
Test the connection with the ad hoc
ping
command.ansible ol-host -i ~/ol-playbook/inventory -m ping -u opc
-u
: Passes the username for the ssh connection. In the free lab environment, we use theopc
user, the default user provided on Oracle Linux instances in Oracle Cloud Infrastructure (OCI). Approve the ECDSA key fingerprint by typingyes
at the prompt to continue.
The command runs successfully with results similar to those shown.
Example output:
ol-host | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" }
Run the playbook.
ansible-playbook -i inventory setup.yml -u opc
The command should run successfully, showing each play's changed result.
Connect to the Remote Host
If the playbook runs successfully, we can connect to the remote system with the user as defined in the username variable.
Connect via SSH to the ol-host system.
ssh oracle@$REMOTE
If you changed the
ssh_key_file
variable's value, you would need to pass the `-i' option to ssh, pointing to the private key file of the specified pair.Example:
ssh -i ~/.ssh/<local_ssh_private_key> <username>@<ip_address_of_host>
Verify installation of the requested packages.
After logging in, you can verify that the playbook has installed the
git
package.git --version
Disconnect from the remote host.
exit
Add a Repository
Oracle Linux provides a secure, scalable, and reliable platform where you can deploy your mission-critical applications. Adding an extra repository is used to install a new Oracle product or a third-party application. Oracle Linux provides supplemental packages that handle the provisioning of these additional repositories.
The Oracle Linux YUM server provides details of the many different repositories Oracle provides.
Add these additional tasks to the existing playbook file.
cat << EOF | tee -a setup.yml > /dev/null - name: Add the EPEL repository ansible.builtin.dnf: name: oracle-epel-release-el8 state: present when: - ansible_distribution == 'OracleLinux' - ansible_facts['distribution_major_version'] == '8' - name: Install the htop utility package ansible.builtin.dnf: name: htop enablerepo: ol8_developer_EPEL state: present when: - ansible_distribution == 'OracleLinux' - ansible_facts['distribution_major_version'] == '8' EOF
This playbook adds two tasks within the defined play. The first adds the EPEL repository to the existing software repositories on the Oracle Linux instance. Meanwhile, the second play installs the htop package onto the same target host instance.
Parameters worth noting:
state: present
: Ensures the referenced package is already on the system or gets installed usingdnf
.enablerepo
: Enables the specific repository (if disabled) only for the duration of the current task.
Rerun the playbook to perform the additional tasks.
ansible-playbook -i inventory setup.yml -u opc
The playbook finishes successfully, showing a few warnings that we can ignore. The playbook reports properly completed tasks with an ok or changed status. The changed status indicates that the playbook modified the host by adding the htop package or updating the dnf cache when requesting the latest version of a package rather than just checking if it is present. An ok indicates the completion of the task and requires no action.
Verify the installation of the repository and package.
ssh oracle@$REMOTE which htop
Run the
htop
command.ssh oracle@$REMOTE -t htop
The SSH command's
-t
option forces a TTY allocation as htop requires an interactive shell.Exit the htop program by typing
q
.
Add, Update, and Delete Files and Directories
Create a directory and file by adding the following to the existing playbook file.
cat << EOF | tee -a setup.yml > /dev/null - name: Create an example directory (if it does not already exist) ansible.builtin.file: path: "/home/{{ username }}/example" state: directory mode: '0755' become_user: "{{ username }}" - name: Create an empty file ansible.builtin.file: path: "/home/{{ username }}/example/test.txt" state: touch # mode: u=rw,g=r,o=r become_user: "{{ username }}" EOF
This playbook adds two tasks within the defined play. The first creates a new directory called example in the remote user's home directory, and the second creates an empty file called test.txt in the newly created example directory.
Additional parameters worth noting:
state:
: The ansible.builtin.file module supports the following parameters: absent, directory, file, hard, link and touch. These tasks use directory, which creates the directory path if not already present, and touch, which creates an empty file if not already present.mode
: Sets the filesystem permissions for the object created. When using the command line, you can use either octal 0644 or symbolic u=rw,g=r,o=r modes. The system default mode is applied if you omit the mode: parameter.become_user: "{{ username }}"
: Uses the Oracle Linux Automation Engine privilege escalation functionality to execute a task. In previous tutorials, we introduced thebecome:
privilege escalation functionality to run a task as root. This example illustrates how to use similar functionality to execute a task as another user. We set the user as the variable"{{ username }}"
, which we predefined as the oracle user in the vars/defaults.yml file. Hence, this task runs as the oracle user and inherits that user's defaults.
Run the playbook to perform the additional tasks.
ansible-playbook -i inventory setup.yml -u opc
Confirm the new directory and file exists on the remote host.
ssh oracle@$REMOTE ls -al example
The output confirms the creation of a new directory and the empty file. Notice that the test.txt file has zero bytes, and the oracle user owns it.
Add multiple files.
During the setup of a system, you may need to create several files within a directory. Rather than using individual tasks, you can create several files in a directory as an atomic operation.
cat << EOF | tee -a setup.yml > /dev/null - name: Add multiple files ansible.builtin.file: path: "/home/{{ username }}/example/{{ item }}" state: touch with_items: - file01.txt - file02.txt - file03.txt - file04.txt become_user: "{{ username }}" EOF
This task uses a loop to create several empty files from a list.
path:
: Defines the location on the remote system to write the files. Oracle Linux Automation Engine replaces the{{ item }}
variable with eachitem
from thewith_items
parameter during runtime.with_items:
: This parameter indicates the list ofitems
to loop over during the running of this task. While this example uses only four file names, you can make the list as long as you need.
Run the playbook to perform the additional tasks.
ansible-playbook -i inventory setup.yml -u opc
Confirm the new directory and file exists on the remote host.
ssh oracle@$REMOTE ls -al example
The output displays the original test.txt file and the new ones.
Add a single line of content to a file.
cat << 'EOF' | tee -a setup.yml > /dev/null - name: Insert some text into the test.txt file ansible.builtin.lineinfile: path: "/home/{{ username }}/example/test.txt" line: This is a test create: True owner: "{{ username }}" group: "{{ username }}" mode: '0644' - name: Add an extra line after the line just inserted ansible.builtin.lineinfile: path: "/home/{{ username }}/example/test.txt" regexp: '^a test' insertafter: 'This is a test' line: This is some updated text that was added later. create: True owner: "{{ username }}" group: "{{ username }}" mode: '0644' - name: Get the contents of the test.txt file ansible.builtin.command: cat ~/example/test.txt register: command_output become_user: "{{ username }}" - name: Print the results of the cat command ansible.builtin.debug: msg: "{{ command_output }}" - name: Print only the lines added to the text file ansible.builtin.debug: msg: "{{ command_output.stdout_lines }}" EOF
The first two tasks use the ansible.builtin.lineinfile to add and update single lines in the file.
The remaining tasks show a method to confirm the changes. The playbook first uses the ansible.builtin.command to output the content of the updated file to stdout and saves it to memory in a
register
variable. Then it uses the ansible.builtin.debug module to print that content to the screen as part of the playbook output. The second debug task shows a way to limit the output to only a particular part of the JSON output from the previous task.
Run the playbook to perform the additional tasks.
ansible-playbook -i inventory setup.yml -u opc
The last task in the playbook prints the file's contents using the debug module.
Add multiple lines of content to a file.
cat << EOF | tee -a setup.yml > /dev/null - name: Add two lines into test.txt ansible.builtin.blockinfile: path: "/home/{{ username }}/example/test.txt" insertafter: 'This is some updated text that was added later.' block: | "Welcome to {{ ansible_hostname }}" "Last updated on {{ ansible_date_time.iso8601 }}" - name: Create a new file and insert content into it ansible.builtin.copy: content: | === The text below was added by Oracle Linux Automation Engine ========== Hello from the ansible.builtin.copy module. This task is an example of inserting multiple lines into a text file. You can insert more lines if you want. === The text above was added by Oracle Linux Automation Engine =========== dest: "/home/{{ username }}/example/testing.txt" mode: '0644' become_user: "{{ username }}" EOF
Run the playbook to perform the additional tasks.
ansible-playbook -i inventory setup.yml -u opc
Confirm two new lines are present in the file.
ssh oracle@$REMOTE cat example/test.txt
The output shows the added content and interprets the task's variable values based on the automatic fact-gathering done by the playbook against the remote host during runtime.
Confirm creating the new file and adding the content.
ssh oracle@$REMOTE cat example/testing.txt
Delete files and directories.
Besides creating files and directories and adding text, Oracle Linux Automation Engine can also delete these items. Let's remove the directory and all the files this playbook creates and leave the system in the same state as when we started. These tasks first use the state: parameter set to absent to remove the list of files and then the directory. The second task handles removing the files in a single step along with the directory. Still, we include the first task for additional demonstration of the Oracle Linux Automation Engine features.
cat << EOF | tee -a setup.yml > /dev/null - name: Delete multiple files ansible.builtin.file: path: '{{ item }}' state: absent with_items: - "/home/{{ username }}/example/test.txt" - "/home/{{ username }}/example/file01.txt" - "/home/{{ username }}/example/file02.txt" - "/home/{{ username }}/example/file03.txt" - "/home/{{ username }}/example/file04.txt" - "/home/{{ username }}/example/testing.txt" - name: Recursively remove directory ansible.builtin.file: path: "/home/{{ username }}/example" state: absent EOF
It is possible to recursively delete a directory and any contents using the second task shown. The first task, deleting the files individually, is present in case you only want to delete files. If you want to delete both files and files and directories, the second task is more efficient.
Run the playbook to perform the additional tasks.
ansible-playbook -i inventory setup.yml -u opc
Verify the removal of the specified directory.
ssh oracle@$REMOTE ls
The lack of output confirms the specified directory and the files created during this tutorial are gone.
Next Steps
Congratulations on making it this far. This tutorial introduced several ways Oracle Linux Automation Engine makes performing routine tasks on Oracle Linux possible through automation, such as installing packages, creating, updating, and deleting files and directories, and adding user accounts to Oracle Linux. We also showed how to use Oracle Linux Automation Engine's ansible.builtin.debug
module to return information to the terminal while executing a Playbook. With these skills, you have a head start to venture forward and write your own playbooks.