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:
- vmware_tag – Manage VMware vSphere Tags
- vmware_tag_facts – Retrieve VMware vSphere Tag facts
- vmware_tag_manager – Manage association of VMware vSphere Tags with vCenter objects
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.

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.

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.

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