Run Oracle Linux Tasks with Oracle Linux Automation Engine

5
0
Send lab feedback

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.

  1. Open a terminal on the Luna Desktop.

  2. Clone the linux-virt-labs GitHub project.

    git clone https://github.com/oracle-devrel/linux-virt-labs.git
  3. Change into the working directory.

    cd linux-virt-labs/olam
  4. Install the required collections.

    ansible-galaxy collection install -r requirements.yml
  5. 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
  6. 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 sets ansible_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.

  1. Open a new terminal and connect via SSH to the ol-control-node system.

    ssh oracle@<ip_address_of_instance>
  2. Verify that the Oracle Linux Automation Engine command is available.

    ansible --version
  3. Create a working project directory.

    mkdir -p ~/ol-playbook
  4. Create a variable directory and file for the project.

    mkdir ~/ol-playbook/vars
    touch ~/ol-playbook/vars/defaults.yml
  5. 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 be oracle.
    • user_default_password: The default password for the oracle user when created. The password is required when running sudo 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 key id_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 as container-tools, the array would look like ['git',' @container-tools:ol8'].
  6. 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 the sudo 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.

  1. Create a requirements file.

    cat << 'EOF' | tee ~/ol-playbook/requirements.yml > /dev/null
    ---
    collections:
      - name: ansible.posix
      - name: community.crypto
    EOF
  2. 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 for ansible. 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.

  1. 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>
  2. Create a new inventory file project directory.

    cat << EOF | tee ~/ol-playbook/inventory > /dev/null
    [production]
    ol-host ansible_host=$REMOTE
    EOF
  3. Change to the project working directory.

    cd ~/ol-playbook
  4. 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 the opc user, the default user provided on Oracle Linux instances in Oracle Cloud Infrastructure (OCI). Approve the ECDSA key fingerprint by typing yes 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"
    }
  5. 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.

  1. 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>
  2. Verify installation of the requested packages.

    After logging in, you can verify that the playbook has installed the git package.

    git --version
  3. 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.

  1. 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 using dnf.
    • enablerepo: Enables the specific repository (if disabled) only for the duration of the current task.
  2. 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.

  3. Verify the installation of the repository and package.

    ssh oracle@$REMOTE which htop
  4. 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.

  5. Exit the htop program by typing q.

Add, Update, and Delete Files and Directories

  1. 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 the become: 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.
  2. Run the playbook to perform the additional tasks.

    ansible-playbook -i inventory setup.yml -u opc
  3. 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.

  4. 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 each item from the with_items parameter during runtime.
    • with_items:: This parameter indicates the list of items 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.
  5. Run the playbook to perform the additional tasks.

    ansible-playbook -i inventory setup.yml -u opc
  6. 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.

  7. 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.

  8. 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.

  9. 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
  10. Run the playbook to perform the additional tasks.

    ansible-playbook -i inventory setup.yml -u opc
  11. 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.

  12. Confirm creating the new file and adding the content.

    ssh oracle@$REMOTE cat example/testing.txt
  13. 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.

  14. Run the playbook to perform the additional tasks.

    ansible-playbook -i inventory setup.yml -u opc
  15. 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.

SSR