Veeam and vSphere Tag Integration with Ansible

VMware vSphere Tags are a great tool to build a deep integration with other VMware or third-party products. Veeam already could use vSphere Tags as a backup object in Veeam Backup & Replication 8.0 Update 2b (Thanks to Jim Jones and Luciano Patrão for helping me out with this info). This article will show how a Veeam and vSphere Tag Integration with Ansible can be done. I would like to demonstrate at first what is possible with the Ansible standard community modules and then also the possibility to create a Veeam backup job with vSphere Tags as a backup object with my own Ansible Veeam module.

That vSphere Tags can be used for a very slick integration into Veeam Backup & Replication is not a new topic. One of my blogger fellows Falko Banaszak, for example, wrote a great blog post about that topic and I have also shown some examples in my Veeam PowerShell Webinar last year.

vSphere Tag management with Ansible

The Ansible community has created three VMware vSphere Ansible modules for Tag handling:

This Ansible playbook creates a set of new VMs and assigns a newly created vSphere Tag “DemoApp” of the Tag category “Protection”. The Tag category “Protection” already exists and the Tag “Default” is known.

- name: Manage vSphere VM Tags
  hosts: veeam
  gather_facts: no
  vars:
    tag_category: Protection
    tag_name: DemoApp
    vm_cluster: cluster01
    vm_datacenter: lab
    vm_folder: /{{ vm_datacenter }}/vm/
    vm_list:  
      - { name: 'DemoApp01', datastore: 'datastore1', network: 'DPortGroup1', cpu: '1', mem: '256' }
      - { name: 'DemoApp02', datastore: 'datastore1', network: 'DPortGroup2', cpu: '1', mem: '256' }
  tasks:
    - name: Create new vSphere VMs
      vmware_guest:
            hostname: "{{ vcenter_hostname }}"
            username: "{{ vcenter_username }}"
            password: "{{ vcenter_password }}"
            validate_certs: no
            datacenter: "{{ vm_datacenter }}"
            cluster: "{{ vm_cluster }}"
            folder: "{{ vm_folder }}"
            name: "{{ item.name }}"
            state: poweredoff
            guest_id: centos64Guest
            datastore: "{{ item.datastore }}"
            disk:
            - size_gb: 10
              type: thin
            hardware:
              memory_mb: "{{ item.mem }}"
              num_cpus: "{{ item.cpu }}"
              scsi: paravirtual
              boot_firmware: efi
            networks:
            - name: "{{ item.network }}"
              device_type: vmxnet3
            customvalues:
            - key: "svga.vgaOnly"
              Value: true
      delegate_to: localhost
      with_items:  "{{ vm_list }}"
    - name: Get category id from the given tag
      vmware_tag_facts:
        hostname: '{{ vcenter_hostname }}'
        username: '{{ vcenter_username }}'
        password: '{{ vcenter_password }}'
        validate_certs: no
      register: tag_details
      delegate_to: localhost
    - name: Create Tag for '{{ tag_name }}'
      vmware_tag:
        hostname: '{{ vcenter_hostname }}'
        username: '{{ vcenter_username }}'
        password: '{{ vcenter_password }}'
        validate_certs: no
        category_id: "{{ tag_details.tag_facts['Default']['tag_category_id'] }}"
        tag_name: '{{ tag_name }}'
        state: present
      delegate_to: localhost
    - name: Add Tag '{{ tag_name }}' to VMs
      vmware_tag_manager:
        hostname: '{{ vcenter_hostname }}'
        username: '{{ vcenter_username }}'
        password: '{{ vcenter_password }}'
        validate_certs: no
        tag_names:
          - "{{ tag_category }}:{{ tag_name }}"
        object_name: "{{ item.name }}"
        object_type: VirtualMachine
        state: add
      with_items:  "{{ vm_list }}"
      delegate_to: localhost

The result is two VMware vSphere VMs with the newly created tag assigned.

Veeam and vSphere Tag Integration with Ansible - PowerCLI console - Playbook result

If you now create a Veeam backup job that uses the newly created Tag as an object, both VMs are gathered from the inventory and will be processed during the backup.

Veeam and vSphere Tag Integration with Ansible - Veeam Backup Job

Create Veeam backup job with Ansible

But since my goal is to achieve the highest possible degree of automation, I would also like to have the Backup job created with Ansible. To achieve this, I have extended my Ansible Veeam module (also shipped as a Role on Ansible Galaxy). The module is now able to create new Veeam backup jobs with Tags as backup objects.

PowerShell code of the new veeam_backup module:

#!powershell

# Copyright: (c) 2019, Markus Kraus <[email protected]>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#Requires -Module Ansible.ModuleUtils.Legacy
#AnsibleRequires -OSVersion 6.2
#AnsibleRequires -CSharpUtil Ansible.Basic

$spec = @{
    options = @{
        type = @{ type = "str"; choices = "vi", "hv"; default = "vi" }
        entity = @{ type = "str"; choices = "tag", "vm"; default = "tag" }
        tag = @{ type = "str" }
        state = @{ type = "str"; choices = "absent", "present"; default = "present" }
        name = @{ type = "str" }
        repository = @{ type = "str" }
        id = @{ type = "str" }

    }
    required_if = @(@("state", "present", @("type", "entity", "name", "repository")),
                    @("state", "absent", @("id")))
    supports_check_mode = $true
}

$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)

$ErrorActionPreference = "Stop"
Set-StrictMode -Version 2.0

# Functions
Function Connect-VeeamServer {
    try {
        Add-PSSnapin -PassThru VeeamPSSnapIn -ErrorAction Stop | Out-Null
    }
    catch {
        Fail-Json -obj @{} -message  "Failed to load Veeam SnapIn on the target: $($_.Exception.Message)"  
            
    }

    try {
        Connect-VBRServer -Server localhost
    }
    catch {
        Fail-Json -obj @{} -message "Failed to connect VBR Server on the target: $($_.Exception.Message)"  
    }
}
Function Disconnect-VeeamServer {
    try {
        Disconnect-VBRServer
    }
    catch {
        Fail-Json -obj @{} -message "Failed to disconnect VBR Server on the target: $($_.Exception.Message)"  
    }
}

# Connect
Connect-VeeamServer

switch ( $module.Params.state) {
    "present" { $Repositroy = Get-VBRBackupRepository -Name $module.Params.repository
                    switch ($module.Params.type) {
                        "vi" {      switch ($module.Params.entity) {
                                        "tag" { $Entity = Find-VBRViEntity -Tags | Where-Object {$_.Path -match "$($module.Params.tag)"} }
                                        "vm" { Fail-Json -obj @{} -message "Type not yet implemented."}
                                        Default { }
                                    }
                            
                                    $Backup = Add-VBRViBackupJob -Name $module.Params.name -Entity $Entity -BackupRepository $Repositroy
                                    $module.Result.changed = $true
                                    $module.Result.id = $Backup.id  
                                }
                        "hv" { Fail-Json -obj @{} -message "Type not yet implemented."
                            
                                    }
                        Default { }
                    }
                }
    "absent" { Fail-Json -obj @{} -message "State not yet implemented."
                }
    Default {}
}

# Disconnect
Disconnect-VeeamServer

# Return result
$module.ExitJson()

Example Playbook to add a new Veeam backup job:

- name: Add new Backup Job
  hosts: veeam
  gather_facts: no
  roles:
  - veeam
  vars:
    query: "veeam_facts.veeam_backups[?id=='{{ my_backup.id }}']"
  tasks:
  - name: Create Backup Job
    veeam_backup:
        state: present
        type: vi
        entity: tag
        tag: "Protection\\\\Default"
        name: BackupJob01
        repository: "Default Backup repository"
    register: my_backup
  - name: Get Veeam Facts
    veeam_connection_facts:
    register: my_facts
  - name: Debug Veeam Backup Job Facts
    debug:
        var: my_facts | json_query(query)

End-to-end Veeam and vSphere Tag Integration with Ansible

Bothe examples combined in one playbook form an end-to-end Veeam and vSphere Tag Integration with Ansible. Deployment of new VMware vSphere VMs (our app), a new vSphere Tag that represents the new app and a new Veeam Backup Job that uses the vSphere Tag to protect the new app.

Veeam and vSphere Tag Integration with Ansible - Playbook run
- name: Manage vSphere VM Tags with new Veeam Backup
  hosts: veeam
  gather_facts: no
  roles:
  - veeam
  vars:
    query: "veeam_facts.veeam_backups[?id=='{{ my_backup.id }}']"
    tag_category: Protection
    tag_name: DemoApp
    vm_cluster: cluster01
    vm_datacenter: lab
    vm_folder: /{{ vm_datacenter }}/vm/
    vm_list:  
      - { name: 'DemoApp01', datastore: 'datastore1', network: 'DPortGroup1', cpu: '1', mem: '256' }
      - { name: 'DemoApp02', datastore: 'datastore1', network: 'DPortGroup2', cpu: '1', mem: '256' }
  tasks:
    - name: Create new vSphere VMs
      vmware_guest:
            hostname: "{{ vcenter_hostname }}"
            username: "{{ vcenter_username }}"
            password: "{{ vcenter_password }}"
            validate_certs: no
            datacenter: "{{ vm_datacenter }}"
            cluster: "{{ vm_cluster }}"
            folder: "{{ vm_folder }}"
            name: "{{ item.name }}"
            state: poweredoff
            guest_id: centos64Guest
            datastore: "{{ item.datastore }}"
            disk:
            - size_gb: 10
              type: thin
            hardware:
              memory_mb: "{{ item.mem }}"
              num_cpus: "{{ item.cpu }}"
              scsi: paravirtual
              boot_firmware: efi
            networks:
            - name: "{{ item.network }}"
              device_type: vmxnet3
            customvalues:
            - key: "svga.vgaOnly"
              Value: true
      delegate_to: localhost
      with_items:  "{{ vm_list }}"
    - name: Get category id from the given tag
      vmware_tag_facts:
        hostname: '{{ vcenter_hostname }}'
        username: '{{ vcenter_username }}'
        password: '{{ vcenter_password }}'
        validate_certs: no
      register: tag_details
      delegate_to: localhost
    - name: Create Tag for '{{ tag_name }}'
      vmware_tag:
        hostname: '{{ vcenter_hostname }}'
        username: '{{ vcenter_username }}'
        password: '{{ vcenter_password }}'
        validate_certs: no
        category_id: "{{ tag_details.tag_facts['Default']['tag_category_id'] }}"
        tag_name: '{{ tag_name }}'
        state: present
      delegate_to: localhost
    - name: Add Tag '{{ tag_name }}' to VMs
      vmware_tag_manager:
        hostname: '{{ vcenter_hostname }}'
        username: '{{ vcenter_username }}'
        password: '{{ vcenter_password }}'
        validate_certs: no
        tag_names:
          - "{{ tag_category }}:{{ tag_name }}"
        object_name: "{{ item.name }}"
        object_type: VirtualMachine
        state: add
      with_items:  "{{ vm_list }}"
      delegate_to: localhost
    - name: Create Backup Job
      veeam_backup:
        state: present
        type: vi
        entity: tag
        tag: "{{ tag_category }}\\\\{{ tag_name }}"
        name: "Backup_{{ tag_name }}"
        repository: "Default Backup repository"
      register: my_backup
    - name: Get Veeam Facts
      veeam_connection_facts:
      register: my_facts
    - name: Debug Veeam Backup Job Facts
      debug:
        var: my_facts | json_query(query)
      

The return of the vmware_tag_facts module is a bit strange if you only need to gather a Tag category id. I have done the workaround to gather the Tag category id of an existing, known Tag (new issue to initiate improvement of the module).

Like the other Ansible Veeam modules, the new veeam_backup module is still under development and offers plenty of room for improvement.
Feel free to open a GitHub Issue, write a comment to this blog post or open a new pull request if you like to contribute to the project.

Leave a Reply