diff --git a/client/v3/v3_service.go b/client/v3/v3_service.go index 5ee59ea91..094108908 100644 --- a/client/v3/v3_service.go +++ b/client/v3/v3_service.go @@ -23,6 +23,7 @@ type Service interface { GetVM(uuid string) (*VMIntentResponse, error) ListVM(getEntitiesRequest *DSMetadata) (*VMListIntentResponse, error) UpdateVM(uuid string, body *VMIntentInput) (*VMIntentResponse, error) + CloneVM(uuid string, body *VMCloneInput) (*VMCloneResponse, error) CreateSubnet(createRequest *SubnetIntentInput) (*SubnetIntentResponse, error) DeleteSubnet(uuid string) (*DeleteResponse, error) GetSubnet(uuid string) (*SubnetIntentResponse, error) @@ -2428,3 +2429,17 @@ func (op Operations) UpdateAddressGroup(uuid string, body *AddressGroupInput) er return op.client.Do(ctx, req, nil) } + +func (op Operations) CloneVM(uuid string, body *VMCloneInput) (*VMCloneResponse, error) { + ctx := context.TODO() + + path := fmt.Sprintf("/vms/%s/clone", uuid) + req, err := op.client.NewRequest(ctx, http.MethodPost, path, body) + vmIntentResponse := new(VMCloneResponse) + + if err != nil { + return nil, err + } + + return vmIntentResponse, op.client.Do(ctx, req, vmIntentResponse) +} diff --git a/client/v3/v3_structs.go b/client/v3/v3_structs.go index b6f8753fc..5146fd749 100644 --- a/client/v3/v3_structs.go +++ b/client/v3/v3_structs.go @@ -2600,3 +2600,24 @@ type AddressGroupListResponse struct { Metadata *ListMetadataOutput `json:"metadata,omitempty"` Entities []*AddressGroupListEntry `json:"entities,omitempty"` } + +type OverrideSpec struct { + Name *string `json:"name,omitempty"` + NumSockets *int `json:"num_sockets,omitempty"` + NumVcpusPerSocket *int `json:"num_vcpus_per_socket,omitempty"` + NumThreadsPerCore *int `json:"num_threads_per_core,omitempty"` + MemorySizeMib *int `json:"memory_size_mib,omitempty"` + NicList []*VMNic `json:"nic_list,omitempty"` + BootConfig *VMBootConfig `json:"boot_config,omitempty"` + GuestCustomization *GuestCustomization `json:"guest_customization,omitempty"` +} + +type VMCloneInput struct { + Metadata *Metadata `json:"metadata,omitempty"` + OverrideSpec *OverrideSpec `json:"override_spec,omitempty"` +} + +type VMCloneResponse struct { + TaskUUID *string `json:"task_uuid,omitempty"` + CloneVMUUID *string `json:"clone_vm_uuid,omitempty"` +} diff --git a/nutanix/provider.go b/nutanix/provider.go index 7cad6cbdb..6dfed190d 100644 --- a/nutanix/provider.go +++ b/nutanix/provider.go @@ -183,6 +183,7 @@ func Provider() *schema.Provider { "nutanix_foundation_image": resourceNutanixFoundationImage(), "nutanix_foundation_central_image_cluster": resourceNutanixFCImageCluster(), "nutanix_foundation_central_api_keys": resourceNutanixFCAPIKeys(), + "nutanix_virtual_machine_clone": resourceNutanixVirtualMachineClone(), }, ConfigureContextFunc: providerConfigure, } diff --git a/nutanix/resource_nutanix_virtual_machine.go b/nutanix/resource_nutanix_virtual_machine.go index c0edf290a..7ffcaa374 100644 --- a/nutanix/resource_nutanix_virtual_machine.go +++ b/nutanix/resource_nutanix_virtual_machine.go @@ -52,585 +52,7 @@ func resourceNutanixVirtualMachine() *schema.Resource { Version: 0, }, }, - Schema: map[string]*schema.Schema{ - "cloud_init_cdrom_uuid": { - Type: schema.TypeString, - Computed: true, - Optional: true, - }, - "metadata": { - Type: schema.TypeMap, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "categories": categoriesSchema(), - "project_reference": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "owner_reference": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "api_version": { - Type: schema.TypeString, - Computed: true, - }, - "name": { - Type: schema.TypeString, - Required: true, - }, - "description": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "availability_zone_reference": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "cluster_uuid": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringMatch( - regexp.MustCompile( - "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"), - "please see http://developer.nutanix.com/reference/prism_central/v3/api/models/cluster-reference"), - }, - "cluster_name": { - Type: schema.TypeString, - Computed: true, - }, - "state": { - Type: schema.TypeString, - Computed: true, - }, - "host_reference": { - Type: schema.TypeMap, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "hypervisor_type": { - Type: schema.TypeString, - Computed: true, - }, - "nic_list_status": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "nic_type": { - Type: schema.TypeString, - Computed: true, - }, - "uuid": { - Type: schema.TypeString, - Computed: true, - }, - "floating_ip": { - Type: schema.TypeString, - Computed: true, - }, - "model": { - Type: schema.TypeString, - Computed: true, - }, - "network_function_nic_type": { - Type: schema.TypeString, - Computed: true, - }, - "mac_address": { - Type: schema.TypeString, - Computed: true, - }, - "ip_endpoint_list": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "ip": { - Type: schema.TypeString, - Computed: true, - }, - "type": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "network_function_chain_reference": { - Type: schema.TypeMap, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "num_queues": { - Type: schema.TypeInt, - Computed: true, - }, - "subnet_uuid": { - Type: schema.TypeString, - Computed: true, - }, - "subnet_name": { - Type: schema.TypeString, - Computed: true, - }, - "is_connected": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - - // RESOURCES ARGUMENTS - - "enable_cpu_passthrough": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "is_vcpu_hard_pinned": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "use_hot_add": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "num_vnuma_nodes": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "nic_list": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "nic_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "uuid": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "model": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "network_function_nic_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "mac_address": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "ip_endpoint_list": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "ip": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - }, - }, - }, - "network_function_chain_reference": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "num_queues": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "subnet_uuid": { - Type: schema.TypeString, - Optional: true, - }, - "subnet_name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "is_connected": { - Type: schema.TypeString, - Optional: true, - Default: "true", - }, - }, - }, - }, - "guest_os_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "power_state": { - Type: schema.TypeString, - Computed: true, - }, - "nutanix_guest_tools": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "ngt_credentials": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - }, - "ngt_enabled_capability_list": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "num_vcpus_per_socket": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "num_sockets": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "gpu_list": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "frame_buffer_size_mib": { - Type: schema.TypeInt, - Computed: true, - }, - "vendor": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "uuid": { - Type: schema.TypeString, - Computed: true, - }, - "name": { - Type: schema.TypeString, - Computed: true, - }, - "pci_address": { - Type: schema.TypeString, - Computed: true, - }, - "fraction": { - Type: schema.TypeInt, - Computed: true, - }, - "mode": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "num_virtual_display_heads": { - Type: schema.TypeInt, - Computed: true, - }, - "guest_driver_version": { - Type: schema.TypeString, - Computed: true, - }, - "device_id": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - }, - }, - }, - "parent_reference": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "memory_size_mib": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "boot_device_order_list": { - Type: schema.TypeList, - // // remove MaxItems when the issue #28 is fixed - // MaxItems: 1, - Optional: true, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "boot_device_disk_address": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "boot_device_mac_address": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "boot_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"UEFI", "LEGACY", "SECURE_BOOT"}, false), - }, - "machine_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "hardware_clock_timezone": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "guest_customization_cloud_init_user_data": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - "guest_customization_cloud_init_meta_data": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - "guest_customization_cloud_init_custom_key_values": { - Type: schema.TypeMap, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Optional: true, - Computed: true, - }, - "guest_customization_is_overridable": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - "guest_customization_sysprep": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "guest_customization_sysprep_custom_key_values": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - ForceNew: true, - }, - "should_fail_on_script_failure": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - "enable_script_exec": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - "power_state_mechanism": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "vga_console_enabled": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - "disk_list": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "uuid": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "disk_size_bytes": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "disk_size_mib": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "storage_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "flash_mode": { - Type: schema.TypeString, - Optional: true, - }, - "storage_container_reference": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "url": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "kind": { - Type: schema.TypeString, - Optional: true, - Default: "storage_container", - }, - "name": { - Type: schema.TypeString, - Computed: true, - }, - "uuid": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - }, - }, - }, - }, - }, - }, - "device_properties": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "device_type": { - Type: schema.TypeString, - Optional: true, - Default: "DISK", - }, - "disk_address": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - "data_source_reference": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "volume_group_reference": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - "serial_port_list": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "index": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "is_connected": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - }, - }, - }, - }, + Schema: VMSpecGeneration("create"), } } @@ -2583,3 +2005,610 @@ func resourceNutanixVirtualMachineInstanceResourceV0() *schema.Resource { }, } } + +func VMSpecGeneration(kind string) map[string]*schema.Schema { + sch := map[string]*schema.Schema{ + "cloud_init_cdrom_uuid": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "metadata": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "categories": categoriesSchema(), + "project_reference": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "owner_reference": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "api_version": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "availability_zone_reference": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cluster_name": { + Type: schema.TypeString, + Computed: true, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + "host_reference": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "hypervisor_type": { + Type: schema.TypeString, + Computed: true, + }, + "nic_list_status": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nic_type": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "floating_ip": { + Type: schema.TypeString, + Computed: true, + }, + "model": { + Type: schema.TypeString, + Computed: true, + }, + "network_function_nic_type": { + Type: schema.TypeString, + Computed: true, + }, + "mac_address": { + Type: schema.TypeString, + Computed: true, + }, + "ip_endpoint_list": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "network_function_chain_reference": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "num_queues": { + Type: schema.TypeInt, + Computed: true, + }, + "subnet_uuid": { + Type: schema.TypeString, + Computed: true, + }, + "subnet_name": { + Type: schema.TypeString, + Computed: true, + }, + "is_connected": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + // RESOURCES ARGUMENTS + + "enable_cpu_passthrough": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "is_vcpu_hard_pinned": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "use_hot_add": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "num_vnuma_nodes": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "nic_list": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nic_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "model": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "network_function_nic_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "mac_address": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "ip_endpoint_list": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + "network_function_chain_reference": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "num_queues": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "subnet_uuid": { + Type: schema.TypeString, + Optional: true, + }, + "subnet_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "is_connected": { + Type: schema.TypeString, + Optional: true, + Default: "true", + }, + }, + }, + }, + "guest_os_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "power_state": { + Type: schema.TypeString, + Computed: true, + }, + "nutanix_guest_tools": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "ngt_credentials": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + }, + "ngt_enabled_capability_list": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "num_vcpus_per_socket": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "num_sockets": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "gpu_list": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "frame_buffer_size_mib": { + Type: schema.TypeInt, + Computed: true, + }, + "vendor": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "pci_address": { + Type: schema.TypeString, + Computed: true, + }, + "fraction": { + Type: schema.TypeInt, + Computed: true, + }, + "mode": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "num_virtual_display_heads": { + Type: schema.TypeInt, + Computed: true, + }, + "guest_driver_version": { + Type: schema.TypeString, + Computed: true, + }, + "device_id": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + "parent_reference": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "memory_size_mib": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "boot_device_order_list": { + Type: schema.TypeList, + // // remove MaxItems when the issue #28 is fixed + // MaxItems: 1, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "boot_device_disk_address": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "boot_device_mac_address": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "boot_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"UEFI", "LEGACY", "SECURE_BOOT"}, false), + }, + "machine_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "hardware_clock_timezone": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "guest_customization_cloud_init_user_data": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "guest_customization_cloud_init_meta_data": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "guest_customization_cloud_init_custom_key_values": { + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + Computed: true, + }, + "guest_customization_is_overridable": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "guest_customization_sysprep": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "guest_customization_sysprep_custom_key_values": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + ForceNew: true, + }, + "should_fail_on_script_failure": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "enable_script_exec": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "power_state_mechanism": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "vga_console_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "disk_list": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "uuid": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "disk_size_bytes": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "disk_size_mib": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "storage_config": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "flash_mode": { + Type: schema.TypeString, + Optional: true, + }, + "storage_container_reference": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "url": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "kind": { + Type: schema.TypeString, + Optional: true, + Default: "storage_container", + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "device_properties": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "device_type": { + Type: schema.TypeString, + Optional: true, + Default: "DISK", + }, + "disk_address": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "data_source_reference": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "volume_group_reference": { + Type: schema.TypeMap, + Optional: true, + Computed: true, + + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "serial_port_list": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "index": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "is_connected": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, + } + + if kind == "create" { + sch["name"] = &schema.Schema{ + Type: schema.TypeString, + Required: true, + } + sch["cluster_uuid"] = &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile( + "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"), + "please see http://developer.nutanix.com/reference/prism_central/v3/api/models/cluster-reference"), + } + } + + if kind == "clone" { + sch["source_vm_uuid"] = &schema.Schema{ + Type: schema.TypeString, + Required: true, + } + sch["name"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + } + sch["cluster_uuid"] = &schema.Schema{ + Type: schema.TypeString, + Computed: true, + } + sch["task_uuid"] = &schema.Schema{ + Type: schema.TypeString, + Computed: true, + } + } + + return sch +} diff --git a/nutanix/resource_nutanix_virtual_machine_clone.go b/nutanix/resource_nutanix_virtual_machine_clone.go new file mode 100644 index 000000000..1d5c78407 --- /dev/null +++ b/nutanix/resource_nutanix_virtual_machine_clone.go @@ -0,0 +1,234 @@ +package nutanix + +import ( + "context" + "log" + "reflect" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + v3 "github.com/terraform-providers/terraform-provider-nutanix/client/v3" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func resourceNutanixVirtualMachineClone() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceNutanixVirtualMachineCloneCreate, + UpdateContext: resourceNutanixVirtualMachineCloneUpdate, + ReadContext: resourceNutanixVirtualMachineCloneRead, + DeleteContext: resourceNutanixVirtualMachineCloneDelete, + Schema: VMSpecGeneration("clone"), + } +} + +func resourceNutanixVirtualMachineCloneCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*Client).API + + var id string + vmUUID, nok := d.GetOk("source_vm_uuid") + if nok { + id = *utils.StringPtr(vmUUID.(string)) + } + spec := &v3.VMCloneInput{} + + spec.Metadata = getMetadataCloneAttributes(d) + spec.OverrideSpec = expandOverrideSpec(d) + + // Make request to the API + resp, err := conn.V3.CloneVM(id, spec) + if err != nil { + return diag.FromErr(err) + } + taskUUID := *resp.TaskUUID + + // Wait for the VM to be available + stateConf := &resource.StateChangeConf{ + Pending: []string{"QUEUED", "RUNNING"}, + Target: []string{"SUCCEEDED"}, + Refresh: taskStateRefreshFunc(conn, taskUUID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: vmDelay, + MinTimeout: vmMinTimeout, + } + + taskInfo, errWaitTask := stateConf.WaitForStateContext(ctx) + if errWaitTask != nil { + return diag.Errorf("error waiting for task (%s) to create: %s", taskUUID, errWaitTask) + } + + // Get the cloned VM UUID + var cloneVMUUID string + taskDetails, ok := taskInfo.(*v3.TasksResponse) + if ok { + cloneVMUUID = *taskDetails.EntityReferenceList[0].UUID + } + + // State Changed to Power ON + if er := changePowerState(ctx, conn, cloneVMUUID, "ON"); er != nil { + return diag.Errorf("internal error: cannot turn ON the VM with UUID(%s): %s", cloneVMUUID, err) + } + + // Wait for IP available + waitIPConf := &resource.StateChangeConf{ + Pending: []string{WAITING}, + Target: []string{"AVAILABLE"}, + Refresh: waitForIPRefreshFunc(conn, cloneVMUUID), + Timeout: vmTimeout, + Delay: vmDelay, + MinTimeout: vmMinTimeout, + } + + vmIntentResponse, ero := waitIPConf.WaitForStateContext(ctx) + if ero != nil { + log.Printf("[WARN] could not get the IP for VM(%s): %s", cloneVMUUID, err) + } else { + vm := vmIntentResponse.(*v3.VMIntentResponse) + + if len(vm.Status.Resources.NicList) > 0 && len(vm.Status.Resources.NicList[0].IPEndpointList) != 0 { + d.SetConnInfo(map[string]string{ + "type": "ssh", + "host": *vm.Status.Resources.NicList[0].IPEndpointList[0].IP, + }) + } + } + + // Set terraform state id + d.SetId(cloneVMUUID) + return resourceNutanixVirtualMachineCloneRead(ctx, d, meta) +} + +func resourceNutanixVirtualMachineCloneUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return resourceNutanixVirtualMachineUpdate(ctx, d, meta) +} + +func resourceNutanixVirtualMachineCloneRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return resourceNutanixVirtualMachineRead(ctx, d, meta) +} + +func resourceNutanixVirtualMachineCloneDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return resourceNutanixVirtualMachineDelete(ctx, d, meta) +} + +func getMetadataCloneAttributes(d *schema.ResourceData) (out *v3.Metadata) { + resourceData, ok := d.GetOk("metadata") + if !ok { + return nil + } + + meta := resourceData.([]interface{})[0].(map[string]interface{}) + + if name, ok := meta["uuid"]; ok { + out.UUID = utils.StringPtr(name.(string)) + } + + return out +} + +func expandOverrideSpec(d *schema.ResourceData) *v3.OverrideSpec { + res := &v3.OverrideSpec{} + + if name, ok := d.GetOk("name"); ok { + res.Name = utils.StringPtr(name.(string)) + } + + if numSockets, sok := d.GetOk("num_sockets"); sok { + res.NumSockets = utils.IntPtr(numSockets.(int)) + } + + if vcpuSock, vok := d.GetOk("num_vcpus_per_socket"); vok { + res.NumVcpusPerSocket = utils.IntPtr(vcpuSock.(int)) + } + + if numThreads, ok := d.GetOk("num_threads_per_core"); ok { + res.NumThreadsPerCore = utils.IntPtr(numThreads.(int)) + } + + if memorySize, mok := d.GetOk("memory_size_mib"); mok { + res.MemorySizeMib = utils.IntPtr(memorySize.(int)) + } + + if _, nok := d.GetOk("nic_list"); nok { + res.NicList = expandNicList(d) + } + + guestCustom := &v3.GuestCustomization{} + cloudInit := &v3.GuestCustomizationCloudInit{} + + if v, ok := d.GetOk("guest_customization_cloud_init_user_data"); ok { + cloudInit.UserData = utils.StringPtr(v.(string)) + } + + if v, ok := d.GetOk("guest_customization_cloud_init_meta_data"); ok { + cloudInit.MetaData = utils.StringPtr(v.(string)) + } + + if v, ok := d.GetOk("guest_customization_cloud_init_custom_key_values"); ok { + cloudInit.CustomKeyValues = utils.ConvertMapString(v.(map[string]interface{})) + } + + if !reflect.DeepEqual(*cloudInit, (v3.GuestCustomizationCloudInit{})) { + guestCustom.CloudInit = cloudInit + } + + if v, ok := d.GetOk("guest_customization_is_overridable"); ok { + guestCustom.IsOverridable = utils.BoolPtr(v.(bool)) + } + if v, ok := d.GetOk("guest_customization_sysprep"); ok { + guestCustom.Sysprep = &v3.GuestCustomizationSysprep{} + spi := v.(map[string]interface{}) + if v2, ok2 := spi["install_type"]; ok2 { + guestCustom.Sysprep.InstallType = utils.StringPtr(v2.(string)) + } + if v2, ok2 := spi["unattend_xml"]; ok2 { + guestCustom.Sysprep.UnattendXML = utils.StringPtr(v2.(string)) + } + } + + if v, ok := d.GetOk("guest_customization_sysprep_custom_key_values"); ok { + if guestCustom.Sysprep == nil { + guestCustom.Sysprep = &v3.GuestCustomizationSysprep{} + } + guestCustom.Sysprep.CustomKeyValues = v.(map[string]string) + } + + if !reflect.DeepEqual(*guestCustom, (v3.GuestCustomization{})) { + res.GuestCustomization = guestCustom + } + bootConfig := &v3.VMBootConfig{} + + if v, ok := d.GetOk("boot_device_order_list"); ok { + bootConfig.BootDeviceOrderList = expandStringList(v.([]interface{})) + res.BootConfig = bootConfig + } + + bd := &v3.VMBootDevice{} + da := &v3.DiskAddress{} + if v, ok := d.GetOk("boot_device_disk_address"); ok { + dai := v.(map[string]interface{}) + + if value3, ok3 := dai["device_index"]; ok3 { + if i, err := strconv.ParseInt(value3.(string), 10, 64); err == nil { + da.DeviceIndex = utils.Int64Ptr(i) + } + } + if value3, ok3 := dai["adapter_type"]; ok3 { + da.AdapterType = utils.StringPtr(value3.(string)) + } + bd.DiskAddress = da + bootConfig.BootDevice = bd + res.BootConfig = bootConfig + } + + if bdmac, ok := d.GetOk("boot_device_mac_address"); ok { + bd.MacAddress = utils.StringPtr(bdmac.(string)) + res.BootConfig.BootDevice = bd + } + + if bootType, ok := d.GetOk("boot_type"); ok { + bootConfig.BootType = utils.StringPtr(bootType.(string)) + res.BootConfig = bootConfig + } + return res +} diff --git a/nutanix/resource_nutanix_virtual_machine_clone_test.go b/nutanix/resource_nutanix_virtual_machine_clone_test.go new file mode 100644 index 000000000..f4b4af45b --- /dev/null +++ b/nutanix/resource_nutanix_virtual_machine_clone_test.go @@ -0,0 +1,188 @@ +package nutanix + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNutanixVirtualMachineClone_basic(t *testing.T) { + r := acctest.RandInt() + vmName := acctest.RandomWithPrefix("test-clone-vm") + resourceName := "nutanix_virtual_machine_clone.vm2" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNutanixVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccNutanixVMCloneConfig(r, vmName), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "hardware_clock_timezone", "UTC"), + resource.TestCheckResourceAttr(resourceName, "power_state", "ON"), + resource.TestCheckResourceAttr(resourceName, "memory_size_mib", "186"), + resource.TestCheckResourceAttr(resourceName, "num_sockets", "1"), + resource.TestCheckResourceAttr(resourceName, "num_vcpus_per_socket", "2"), + resource.TestCheckResourceAttr(resourceName, "name", vmName), + ), + }, + }, + }) +} + +func TestAccNutanixVirtualMachineClone_WithBootDeviceOrderChange(t *testing.T) { + r := acctest.RandInt() + vmName := acctest.RandomWithPrefix("test-clone-vm") + resourceName := "nutanix_virtual_machine_clone.vm2" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNutanixVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccNutanixVMCloneConfigWithBootOrder(r, vmName), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixVirtualMachineExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "num_sockets", "2"), + resource.TestCheckResourceAttr(resourceName, "num_vcpus_per_socket", "2"), + resource.TestCheckResourceAttr(resourceName, "name", vmName), + resource.TestCheckResourceAttr(resourceName, "hardware_clock_timezone", "UTC"), + resource.TestCheckResourceAttr(resourceName, "power_state", "ON"), + resource.TestCheckResourceAttr(resourceName, "memory_size_mib", "186"), + resource.TestCheckResourceAttr(resourceName, "boot_device_order_list.0", "DISK"), + resource.TestCheckResourceAttr(resourceName, "boot_device_order_list.1", "NETWORK"), + resource.TestCheckResourceAttr(resourceName, "boot_device_order_list.2", "CDROM"), + ), + }, + }, + }) +} + +func TestAccNutanixVirtualMachineClone_WithBootType(t *testing.T) { + r := acctest.RandInt() + vmName := acctest.RandomWithPrefix("test-clone-vm") + resourceCloneName := "nutanix_virtual_machine_clone.vm2" + resource.Test(t, resource.TestCase{ + PreventPostDestroyRefresh: true, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNutanixVirtualMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccNutanixVMCloneConfigWithBootType(r, vmName), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixVirtualMachineExists(resourceCloneName), + resource.TestCheckResourceAttr(resourceCloneName, "num_sockets", "2"), + resource.TestCheckResourceAttr(resourceCloneName, "num_vcpus_per_socket", "2"), + resource.TestCheckResourceAttr(resourceCloneName, "name", vmName), + resource.TestCheckResourceAttr(resourceCloneName, "hardware_clock_timezone", "UTC"), + resource.TestCheckResourceAttr(resourceCloneName, "power_state", "ON"), + resource.TestCheckResourceAttr(resourceCloneName, "memory_size_mib", "1024"), + resource.TestCheckResourceAttr(resourceCloneName, "boot_type", "UEFI"), + ), + }, + }, + }) +} + +func testAccNutanixVMCloneConfig(r int, name string) string { + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals { + cluster1 = "${data.nutanix_clusters.clusters.entities.0.service_list.0 == "PRISM_CENTRAL" + ? data.nutanix_clusters.clusters.entities.1.metadata.uuid : data.nutanix_clusters.clusters.entities.0.metadata.uuid}" + } + + resource "nutanix_virtual_machine" "vm1" { + name = "test-dou-%d" + cluster_uuid = "${local.cluster1}" + + boot_device_order_list = ["DISK", "CDROM"] + boot_type = "LEGACY" + num_vcpus_per_socket = 1 + num_sockets = 1 + memory_size_mib = 186 + } + + resource "nutanix_virtual_machine_clone" "vm2"{ + source_vm_uuid = nutanix_virtual_machine.vm1.id + name = "%s" + num_vcpus_per_socket = 2 + } + `, r, name) +} + +func testAccNutanixVMCloneConfigWithBootOrder(r int, name string) string { + return fmt.Sprintf(` + data "nutanix_clusters" "clusters" {} + + locals { + cluster1 = "${data.nutanix_clusters.clusters.entities.0.service_list.0 == "PRISM_CENTRAL" + ? data.nutanix_clusters.clusters.entities.1.metadata.uuid : data.nutanix_clusters.clusters.entities.0.metadata.uuid}" + } + + resource "nutanix_virtual_machine" "vm1" { + name = "test-dou-%d" + cluster_uuid = "${local.cluster1}" + + boot_device_order_list = ["DISK", "CDROM", "NETWORK"] + boot_type = "LEGACY" + num_vcpus_per_socket = 1 + num_sockets = 1 + memory_size_mib = 186 + } + resource "nutanix_virtual_machine_clone" "vm2"{ + source_vm_uuid = nutanix_virtual_machine.vm1.id + name = "%s" + num_vcpus_per_socket = 2 + num_sockets = 2 + boot_device_order_list = ["DISK","NETWORK","CDROM"] + } + `, r, name) +} + +func testAccNutanixVMCloneConfigWithBootType(r int, name string) string { + return fmt.Sprintf(` + + data "nutanix_clusters" "clusters" {} + + locals { + cluster1 = "${data.nutanix_clusters.clusters.entities.0.service_list.0 == "PRISM_CENTRAL" + ? data.nutanix_clusters.clusters.entities.1.metadata.uuid : data.nutanix_clusters.clusters.entities.0.metadata.uuid}" + } + + resource "nutanix_virtual_machine" "vm1" { + name = "test-dou-%[1]d" + cluster_uuid = "${local.cluster1}" + boot_type = "LEGACY" + boot_device_order_list = ["DISK", "CDROM"] + num_vcpus_per_socket = 1 + num_sockets = 1 + memory_size_mib = 186 + disk_list { + device_properties { + device_type = "CDROM" + disk_address = { + device_index = 0 + adapter_type = "IDE" + } + } + } + } + + resource "nutanix_virtual_machine_clone" "vm2"{ + source_vm_uuid = nutanix_virtual_machine.vm1.id + name = "%[2]s" + num_vcpus_per_socket = 2 + num_sockets = 2 + memory_size_mib = 1024 + boot_type = "UEFI" + + + } + `, r, name) +} diff --git a/test_config.json b/test_config.json index c82ac2558..806a473c1 100644 --- a/test_config.json +++ b/test_config.json @@ -4,38 +4,48 @@ "user_group_with_distinguished_name": { "distinguished_name": "cn=sspadmins,cn=users,dc=qa,dc=nucalm,dc=io", "display_name": "sspadmins", - "uuid": "a4f29c09-2552-4c01-992f-b96061796c98" + "uuid": "b2f92a45-c320-4b4e-a40f-51809f0d0a4b" }, "permissions": [ { "name": "", - "uuid": "57f9b783-2a85-4684-b543-1976855027d4" + "uuid": "67fc80e8-f0e7-48d4-90f2-097b4018a550" }, { "name": "Delete_ACP", - "uuid": "0696cac3-516b-460e-9bc1-d6a5d1b761d3" + "uuid": "5036942d-34c7-4bfc-bd1d-8a2bfa4c6fb6" } ], "users": [ { "principal_name": "user4@qa.nucalm.io", "expected_display_name": "user4", - "directory_service_uuid": "0791faca-1a48-499e-8171-60801dd41637" + "directory_service_uuid": "f48b10ca-2fdb-5e75-a58f-1f6e895d1134" }, { "principal_name": "user6@qa.nucalm.io", "expected_display_name": "user6", - "directory_service_uuid": "0791faca-1a48-499e-8171-60801dd41637" + "directory_service_uuid": "f48b10ca-2fdb-5e75-a58f-1f6e895d1134" }, { "principal_name": "ssptest3@qa.nucalm.io", "expected_display_name": "ssptest3", - "directory_service_uuid": "0791faca-1a48-499e-8171-60801dd41637" + "directory_service_uuid": "f48b10ca-2fdb-5e75-a58f-1f6e895d1134" } - ], + ], "node_os_version": "ntnx-1.0", "ad_rule_target": { "name": "ADGroup", "values": "sspadmins" + }, + "protection_policy":{ + "local_az":{ + "uuid":"a973cd7b-7696-4ca5-b959-04b5bcb9e683", + "cluster_uuid":"0005ded3-2367-7205-3507-ac1f6b60292f" + }, + "destination_az":{ + "uuid":"97b0cc07-d838-4925-acd7-540c11bd653d", + "cluster_uuid":"0005dc0f-13a7-62e0-185b-ac1f6b6f97e2" + } } } \ No newline at end of file