Harmonize basic set up with Ansible, part 2

Sat, 4 Jan. 2020     Thomas Bendler     ~ 6 min to read

As promised in the previous post Harmonize basic set up with Ansible 1/3 I would like to dig deeper into the details about the common role. This is how it looks like in the filesystem:

roles
└── common
    ├── files
    │   └── user
    │       ├── alias.plugin.zsh
    │       ├── docker.plugin.zsh
    │       ├── functions.plugin.zsh
    │       ├── git.plugin.zsh
    │       ├── jekyll.plugin.zsh
    │       ├── nerd.plugin.zsh
    │       ├── puppet.plugin.zsh
    │       └── ruby.plugin.zsh
    ├── tasks
    │   ├── localtime.yml
    │   ├── main.yml
    │   ├── motd.yml
    │   ├── networking.yml
    │   ├── repositories.yml
    │   ├── tools.yml
    │   ├── upgrade.yml
    │   └── user.yml
    └── templates
        ├── ifcfg-interface.j2
        ├── motd.j2
        ├── network.j2
        └── route-interface.j2

The most important file is the main.yml file. This file defines which tasks will be executed in which order when the common role is called. So we need one file that calls the role (in my case common.yml):

---
# Common playbook needs to executed first
- name: Common configuration shared for all nodes
  hosts: all
  remote_user: root
  gather_facts: true

  roles:
    - common

Once the role is called, it first calls the file main.yml:

---
# Main playbook for common configuration
#- include: networking.yml
- include: motd.yml
- include: localtime.yml
- include: repositories.yml
- include: upgrade.yml
- include: tools.yml
- include: user.yml

Based on the fact that this is only a demonstration I’ve deactivated the network part. This part expects at least three network devices attached to different network segments like internal, backup, MPLS and so on and so forth. As all nodes of this demonstration only have one network interface, this configuration won’t work anyway. So let’s go through the tasks that are active and that will work. First of all, I’ll set the message of the day:

---
- name: Message of the day setup
  template:
    src: motd.j2
    dest: /etc/motd
    owner: root
    group: root
    mode: 0644

This is a pretty simple task, it takes the motd.j2 template (all templates end with j2 because of all are Jinja2 templates) which is stored in the template directory of the common role. This template will be stored on the target node under /etc/motd with the access rights 0644. Let’s have a look at a slightly more complex example, the user configuration. The task itself looks like:

---
# Create all users defined in local_user (group_vars/all.yml)
- name: Create personal user account
  user:
    name: "{{ item }}"
    password: "!"
    shell: /usr/bin/zsh
    groups: wheel
    generate_ssh_key: yes
    ssh_key_bits: 2048
    ssh_key_file: .ssh/id_rsa
  loop: "{{ local_user }}"

# Add local public rsa key to authorized_keys for all users defined in local_user (group_vars/all.yml)
# This one needs to be reworked for multiple named users)
- name: Add remote authorized key to allow future password-less logins
  authorized_key:
    user: "{{ item }}"
    key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
  loop: "{{ local_user }}"

# Add all users defined in local_user (group_vars/all.yml) to sudo administration group
- name: Add personal user to sudoers
  lineinfile:
    path: /etc/sudoers
    regexp: "{{ item }} ALL"
    line: "{{ item }} ALL=(ALL) NOPASSWD: ALL"
    state: present
    validate: "/usr/sbin/visudo -cf %s"
  loop: "{{ local_user }}"

# Install oh-my-zsh for all users defined in local_user (group_vars/all.yml)
- name: Download oh-my-zsh installer
  get_url:
    url: https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh
    dest: /srv/oh-my-zsh-install.sh

- name: Make oh-my-zsh-install.sh executable for all
  file:
    path: /srv/oh-my-zsh-install.sh
    mode: 0755

- name: Execute the zsh-installer.sh
  command:
    cmd: /srv/oh-my-zsh-install.sh
    creates: /home/{{ item }}/.oh-my-zsh
  become: yes
  become_user: "{{ item }}"
  loop: "{{ local_user }}"

- name: Remove oh-my-zsh-install.sh
  file:
    path: /srv/oh-my-zsh-install.sh
    state: absent

# Install typewritten for all users defined in local_user (group_vars/all.yml)
- name: Deploy typewritten theme to oh-my-zsh using git
  git:
    repo: https://github.com/thbe/typewritten.git
    dest: /home/{{ item }}/.oh-my-zsh/custom/themes/typewritten
  loop: "{{ local_user }}"

# Deploy zsh configuration file to all users defined in local_user (group_vars/all.yml)
- name: Deploy local zsh user configuration - .zshrc
  copy:
    src: user/.zshrc
    dest: /home/{{ item }}/.zshrc
    owner: "{{ item }}"
    group: "{{ item }}"
    mode: 0640
  loop: "{{ local_user }}"

# Deploy plugin configuration files to all users defined in local_user (group_vars/all.yml)
- name: Create .profile.d directory
  file:
    path: /home/{{ item }}/.profile.d
    state: directory
    owner: "{{ item }}"
    group: "{{ item }}"
    mode: 0750
  loop: "{{ local_user }}"

- name: Deploy local zsh user configuration - alias.plugin.zsh
  copy:
    src: user/alias.plugin.zsh
    dest: /home/{{ item }}/.profile.d/alias.plugin.zsh
    owner: "{{ item }}"
    group: "{{ item }}"
    mode: 0640
  loop: "{{ local_user }}"

- name: Deploy local zsh user configuration - docker.plugin.zsh
  copy:
    src: user/docker.plugin.zsh
    dest: /home/{{ item }}/.profile.d/docker.plugin.zsh
    owner: "{{ item }}"
    group: "{{ item }}"
    mode: 0640
  loop: "{{ local_user }}"

- name: Deploy local zsh user configuration - functions.plugin.zsh
  copy:
    src: user/functions.plugin.zsh
    dest: /home/{{ item }}/.profile.d/functions.plugin.zsh
    owner: "{{ item }}"
    group: "{{ item }}"
    mode: 0640
  loop: "{{ local_user }}"

- name: Deploy local zsh user configuration - git.plugin.zsh
  copy:
    src: user/git.plugin.zsh
    dest: /home/{{ item }}/.profile.d/git.plugin.zsh
    owner: "{{ item }}"
    group: "{{ item }}"
    mode: 0640
  loop: "{{ local_user }}"

- name: Deploy local zsh user configuration - jekyll.plugin.zsh
  copy:
    src: user/jekyll.plugin.zsh
    dest: /home/{{ item }}/.profile.d/jekyll.plugin.zsh
    owner: "{{ item }}"
    group: "{{ item }}"
    mode: 0640
  loop: "{{ local_user }}"

- name: Deploy local zsh user configuration - nerd.plugin.zsh
  copy:
    src: user/nerd.plugin.zsh
    dest: /home/{{ item }}/.profile.d/nerd.plugin.zsh
    owner: "{{ item }}"
    group: "{{ item }}"
    mode: 0640
  loop: "{{ local_user }}"

- name: Deploy local zsh user configuration - puppet.plugin.zsh
  copy:
    src: user/puppet.plugin.zsh
    dest: /home/{{ item }}/.profile.d/puppet.plugin.zsh
    owner: "{{ item }}"
    group: "{{ item }}"
    mode: 0640
  loop: "{{ local_user }}"

- name: Deploy local zsh user configuration - ruby.plugin.zsh
  copy:
    src: user/ruby.plugin.zsh
    dest: /home/{{ item }}/.profile.d/ruby.plugin.zsh
    owner: "{{ item }}"
    group: "{{ item }}"
    mode: 0640
  loop: "{{ local_user }}"

What does this task do? First, it creates user IDs in a loop. Therefore it looks up local_user which is defined in the group_vars all.yml configuration file:

---
# Users that should be available on all target nodes
local_user:
  - ansible
  - thbe

Each user is transferred into the item variable and process one by one. The user will be created, the password disabled (we’ll use SSH keys instead), the primary shell is set to ZSH, the SSH keys for the users are created and the users are added to the administrator group.

The next step is that my local public key will be added to the authorized_keys of each user so I can log in without a password. The users will be included in the /etc/sudoers configuration and finally, oh-my-zsh will be installed as well as my preferred theme and last but not least, my personal functions and aliases.



Share on: