vmware.vmware_rest icon indicating copy to clipboard operation
vmware.vmware_rest copied to clipboard

vcenter_vmtemplate_libraryitems: clone VM Template incomplete example and no cloud-init example

Open sean-freeman opened this issue 4 months ago • 8 comments

SUMMARY

Documentation of vcenter_vmtemplate_libraryitems contains incomplete example, and no cloud-init example

It should be expected if automation is used, that a clear example of a VM Template clone is provided in the documentation (especially given vcenter_vm Ansible Module can only use source as Instance Clone or Clone of a VM, and can not use a VM Template).

ISSUE TYPE
  • Documentation Report
COMPONENT NAME

vcenter_vmtemplate_libraryitems

ANSIBLE VERSION
  • All

PROPOSED SOLUTION

The following is a working example, although the cloud-init via configuration specification does not appear to work (even though it follows the REST API Specification, as shown with code comments).

The VM Template must be prepared with cloud-init:

  • Edit /etc/cloud/cloud.cfg with data source for VMware (and not OVF).
    # Enable VMware VM Guest OS Customization with cloud-init (set to true for traditional customization)
    disable_vmware_customization: false
    
    # Use allow raw data to directly use the cloud-init metadata and user data files provided by the VMware VM Customization Specification
    # Wait 120 seconds for VMware VM Customization file to be available
    datasource:
      VMware:
        allow_raw_data: true
        vmware_cust_file_max_wait: 60
    
  • Run:
    • vmware-toolbox-cmd config set deployPkg enable-custom-scripts true
    • vmware-toolbox-cmd config set deployPkg wait-cloudinit-timeout 60
    • sudo cloud-init clean --seed --logs to remove cloud-init logs, remove cloud-init seed directory /var/lib/cloud/seed.
      • If using cloud-init versions prior to 22.3.0 then do not use --machine-id parameter.
      • The --machine-id parameter which removes /etc/machine-id may on first reboot cause the OS Network Interfaces to be DOWN which causes the DHCP Request to silently error.
  • Shutdown, then Clone as Template to Library

Ansible Playbook example:


- name: Define variables
  ansible.builtin.set_fact:
    vmware_vm_folder_name: ""
    vmware_vm_cluster_name: ""
    vmware_vm_cluster_host_name: ""
    vmware_vm_cluster_datastore_name: ""
    vmware_vm_content_library_name: ""

    vmware_vm_target_name: ""
    vmware_vm_dns_root_domain: ""
    vmware_vm_ssh_public_key_file_path: ""


- name: Identify VM Folder
  register: register_vmware_vm_folder
  vmware.vmware_rest.vcenter_folder_info:
    names: "{{ vmware_vm_folder_name }}"
    type: VIRTUAL_MACHINE

- name: Identify Datacenter Cluster
  register: register_vmware_vm_cluster
  vmware.vmware_rest.vcenter_cluster_info:
    names: "{{ vmware_vm_cluster_name }}"

- name: Identify Host in Datacenter Cluster
  register: register_vmware_vm_cluster_host
  vmware.vmware_rest.vcenter_host_info:
    names: "{{ vmware_vm_cluster_host_name }}"

- name: Identify Datastore
  register: register_vmware_vm_cluster_datastore
  vmware.vmware_rest.vcenter_datastore_info:
    names: "{{ vmware_vm_cluster_datastore_name }}"

- name: Identify Content Library (to store VM Template)
  register: register_vmware_vm_content_library
  vmware.vmware_rest.content_locallibrary:
    name: "{{ vmware_vm_content_library_name }}"

- name: List all items in Content Library
  register: register_vmware_vm_content_library_items
  vmware.vmware_rest.content_library_item_info:
    library_id: "{{ register_vmware_vm_content_library.id }}"

- name: Identify VMware Template ID
  ansible.builtin.set_fact:
    vmware_vm_template_id: "{{ (register_vmware_vm_content_library_items.value | selectattr('type', '==', 'vm-template') | selectattr('name', '==', vmware_vm_template_name) | first).id }}"


- name: Check if VM exists
  register: register_check_vm_exists
  vmware.vmware_rest.vcenter_vm_info:
    names: "{{ vmware_vm_target_name }}"

# If VM exists
- name: Set VM ID
  when: not register_check_vm_exists.value | length == 0
  ansible.builtin.set_fact:
    register_vmware_vm_cluster_host_id: "{{ register_check_vm_exists.value[0].vm }}" # VM ID

- name: Check VM status
  register: register_provisioned_host_single_info
  when: not register_check_vm_exists.value | length == 0
  vmware.vmware_rest.vcenter_vm:
    vm: "{{ register_vmware_vm_cluster_host_id }}" # VM ID


# Deploy a Virtual Machine from a VM Template in a Content Library
# Doc: https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.vm_admin.doc/GUID-6EA309BC-9113-449C-B668-ACBB363485C3.html
- name: Provision VMware Virtual Machine based upon the VM Template
  register: register_provisioned_host_single
  vmware.vmware_rest.vcenter_vmtemplate_libraryitems:

    placement:
      folder: "{{ (register_vmware_vm_folder.value | first).folder }}"
      # resource_pool: ""
      cluster: "{{ (register_vmware_vm_cluster.value | first).cluster }}"
      host: "{{ (register_vmware_vm_cluster_host.value | first).host }}"

    template_library_item: '{{ vmware_vm_template_id }}' # ID of the Content Library Item with the source VM Template (not OVF) to be cloned and deployed
    state: deploy # Deploy the VM Template defined in template_library_item
    powered_on: false # false when customization specification with cloud-init is required
    session_timeout: 600 # 10 minutes

    name: "{{ vmware_vm_target_name }}"
    description: "{{ vmware_vm_target_name }} created by Ansible"

    # May cause conflict with powered_on parameter
    hardware_customization:
      cpu_update:
        num_cpus: 2
        num_cores_per_socket: 2
      memory_update:
        memory: "{{ 16 * 1024 }}" # MiB
      # nics:

    # Boot Disk will be loaded to this datastore
    disk_storage:
      datastore: "{{ (register_vmware_vm_cluster_datastore.value | first).datastore }}"
      # storage_policy:


# After VM is provisioned, does not return value if previously provisioned
- name: Set VM ID
  when: register_check_vm_exists.value | length == 0
  ansible.builtin.set_fact:
    register_vmware_vm_cluster_host_id: "{{ register_provisioned_host_single.value }}" # Returned from VM provision

- name: Check VM status
  register: register_provisioned_host_single_info
  vmware.vmware_rest.vcenter_vm:
    vm: "{{ register_vmware_vm_cluster_host_id }}"


# Example https://cloudinit.readthedocs.io/en/23.4.1/reference/datasources/vmware.html#walkthrough-of-guestinfo-keys-transport
# Docs https://developer.vmware.com/docs/18555/GUID-75E27FA9-2E40-4CBF-BF3D-22DCFC8F11F7.html
# >> The instance-id key is required. All other keys are optional.
- name: Set cloud-init variables for customization specification
  when: register_provisioned_host_single_info.value.power_state is defined and register_provisioned_host_single_info.value.power_state != "POWERED_ON"
  ansible.builtin.set_fact:
    metadata_yaml:
      instance-id: "{{ vmware_vm_target_name }}"
      hostname: "{{ vmware_vm_target_name }}"
      local-hostname: "{{ vmware_vm_target_name }}"
      network:
        version: 2
        ethernets:
          nics:
            match:
              name: e*
            dhcp4: true
            dhcp6: false
      public_ssh_keys:
        - "{{ lookup('ansible.builtin.file', vmware_vm_ssh_public_key_file_path) }}"

    userdata_yaml_text: |
      #cloud-config

      hostname: {{ vmware_vm_target_name }}
      fqdn: {{ vmware_vm_target_name }}.{{ vmware_vm_dns_root_domain }}

      # timezone: "Etc/UTC"

      disable_root: false

      ssh_pwauth: false

      ssh_deletekeys: true

      ssh:
        emit_keys_to_console: false

      no_ssh_fingerprints: false

      ssh_authorized_keys:
        - {{ lookup('ansible.builtin.file', vmware_vm_ssh_public_key_file_path) }}

      users:
        - name: root
          ssh_authorized_keys:
            - {{ lookup('ansible.builtin.file', vmware_vm_ssh_public_key_file_path) }}
          lock_passwd: false

      write_files:
        - path: /etc/cloud/cloud-init.disabled
          permissions: "0644"
          content: ""


# Doc 1 https://developer.vmware.com/apis/vsphere-automation/latest/vcenter/api/vcenter/vm/vm/guest/customization/put/
# Doc 2 https://developer.vmware.com/docs/18555/GUID-75E27FA9-2E40-4CBF-BF3D-22DCFC8F11F7.html
# >> cloud-init required: metadata as plain-text JSON/YAML, maximum 512KB file size
# >> cloud-init optional: userdata as plain-text in raw cloud-init format with no compression / no base64 encoding, maximum 512KB file size
# Error 400 com.vmware.vapi.std.errors.not_allowed_in_current_state : if the virtual machine vm is not in a powered off state.
- name: Apply customization specification to the VM in Powered Off state
  when: register_provisioned_host_single_info.value.power_state is defined and register_provisioned_host_single_info.value.power_state != "POWERED_ON"
  vmware.vmware_rest.vcenter_vm_guest_customization:
    vm: '{{ register_vmware_vm_cluster_host_id }}'
    configuration_spec:
      cloud_config:
        type: CLOUDINIT
        cloudinit:
          metadata: "{{ metadata_yaml | to_json(ensure_ascii=true) }}"
          userdata: "{{ userdata_yaml_text | trim }}" # remove last newline character
      # linux_config:
    interfaces: []
    global_DNS_settings: {}


- name: Ensure VM is Powered ON
  register: register_vm_power_info
  vmware.vmware_rest.vcenter_vm_power:
    state: start
    vm: "{{ register_vmware_vm_cluster_host_id }}"
  # Wait until VM is powered on
  until: (register_vm_power_info.value.error_type is defined and register_vm_power_info.value.error_type == "ALREADY_IN_DESIRED_STATE")
  retries: 15
  delay: 60

- name: Show VM Information
  register: register_vm_info
  vmware.vmware_rest.vcenter_vm_info:
    vm: '{{ register_vmware_vm_cluster_host_id }}'
  # Wait until VM is powered on
  until: register_vm_info.value.power_state == "POWERED_ON"
  retries: 45
  delay: 20

- name: Get guest networking information (wait until DHCP assigns IP Address for host)
  register: register_vm_nic_info
  vmware.vmware_rest.vcenter_vm_guest_networking_interfaces_info:
    vm: '{{ register_vmware_vm_cluster_host_id }}'
  # Wait until VM Tools is running
  until: (register_vm_nic_info.value.error_type | default("")) != "SERVICE_UNAVAILABLE" and (register_vm_nic_info.value[0].ip.ip_addresses | length) > 0
  retries: 45
  delay: 20

sean-freeman avatar Feb 08 '24 20:02 sean-freeman