From 95b8d590fb2c5eb289f5936dd199232d289acb44 Mon Sep 17 00:00:00 2001 From: Rick Gardner Date: Tue, 30 Jun 2026 15:34:57 -0400 Subject: [PATCH 1/2] Test diskless vm creation on nutanix infra --- .gitignore | 14 ++++++++++--- tfe/main.tf | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ tfe/outputs.tf | 18 +++++++++++++++++ tfe/providers.tf | 15 ++++++++++++++ tfe/terraform.tf | 19 ++++++++++++++++++ tfe/variables.tf | 28 ++++++++++++++++++++++++++ 6 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 tfe/main.tf create mode 100644 tfe/outputs.tf create mode 100644 tfe/providers.tf create mode 100644 tfe/terraform.tf create mode 100644 tfe/variables.tf diff --git a/.gitignore b/.gitignore index a29990f..3ab4770 100644 --- a/.gitignore +++ b/.gitignore @@ -90,9 +90,6 @@ node_modules/ tmp/ .cache/ -# Local .terraform directories -.terraform/ - # .tfstate files *.tfstate *.tfstate.* @@ -127,3 +124,14 @@ override.tf.json # Ignore CLI configuration files .terraformrc terraform.rc + +# Local state (shouldn't exist with cloud backend, but belt-and-suspenders) +*.tfstate +*.tfstate.* +.terraform/ +.terraform.lock.hcl + +# Never commit real variable values / secrets +terraform.tfvars +*.auto.tfvars +secrets.tf diff --git a/tfe/main.tf b/tfe/main.tf new file mode 100644 index 0000000..86533a8 --- /dev/null +++ b/tfe/main.tf @@ -0,0 +1,51 @@ +# --------------------------------------------------------------------------- +# v3-API resources (works on AOS 6.8.1 / your PC version; v4 / *_v2 resources +# require AOS 7.0 + pc.2024.3 which this cluster doesn't run). +# Provider talks to Prism Central. Credentials come from NUTANIX_* env vars +# set as sensitive workspace variables. +# --------------------------------------------------------------------------- + +# Discover the cluster by name. On v3 the data source returns .id (= cluster UUID). +data "nutanix_clusters" "clusters" {} + +locals { + # Build a name->uuid map of clusters registered to PC, then pick ours by name. + # On single-node CE there's one PE cluster; var.cluster_name must match what + # PE/PC shows (you named it "cluster1"). + cluster_uuid = [ + for c in data.nutanix_clusters.clusters.entities : + c.metadata.uuid + if c.name == var.cluster_name + ][0] +} + +# Look up the subnet by name -> returns .id (subnet UUID). +data "nutanix_subnet" "subnet" { + subnet_name = var.subnet_name +} + +resource "nutanix_virtual_machine" "lab" { + name = var.vm_name + description = "TF lab smoke-test VM (Pattern B, v3 API)" + cluster_uuid = local.cluster_uuid + num_sockets = 1 + num_vcpus_per_socket = 1 + memory_size_mib = var.memory_mib # MiB in v3 — no float/whole-number bug + + nic_list { + subnet_uuid = data.nutanix_subnet.subnet.id + } + + # Empty boot disk (no image yet). Goal of this phase: prove HCP -> agent -> + # Prism Central creates a VM. Image + cloud-init come next iteration. + disk_list { + disk_size_mib = var.disk_mib + device_properties { + device_type = "DISK" + disk_address = { + adapter_type = "SCSI" + device_index = 0 + } + } + } +} diff --git a/tfe/outputs.tf b/tfe/outputs.tf new file mode 100644 index 0000000..08b5298 --- /dev/null +++ b/tfe/outputs.tf @@ -0,0 +1,18 @@ +output "vm_uuid" { + description = "The created VM's UUID." + value = nutanix_virtual_machine.lab.id +} + +output "vm_name" { + value = nutanix_virtual_machine.lab.name +} + +# v3 surfaces the learned IP under nic_list_status. Empty until the guest boots +# with an OS that gets an address — wired now for the Phase 4 AAP hand-off. +output "vm_ip_address" { + description = "Assigned IPv4 address, once the guest has booted with an OS." + value = try( + nutanix_virtual_machine.lab.nic_list_status[0].ip_endpoint_list[0].ip, + "" + ) +} diff --git a/tfe/providers.tf b/tfe/providers.tf new file mode 100644 index 0000000..5fd3374 --- /dev/null +++ b/tfe/providers.tf @@ -0,0 +1,15 @@ +# Provider credentials come from sensitive WORKSPACE VARIABLES in HCP, set as +# environment variables (env category): +# NUTANIX_ENDPOINT = 192.168.0.39 (Prism Central host/IP, no scheme, no port) +# NUTANIX_PORT = 9440 +# NUTANIX_USERNAME = admin +# NUTANIX_PASSWORD = (mark Sensitive) +# NUTANIX_INSECURE = true (CE uses a self-signed cert) +# +# Because all five are read from NUTANIX_* env vars, the provider block can be +# nearly empty. Nothing secret lives in code or in git. + +provider "nutanix" { + # All values sourced from NUTANIX_* environment variables in the workspace. + # Left explicit-but-empty here for documentation; remove if you prefer. +} diff --git a/tfe/terraform.tf b/tfe/terraform.tf new file mode 100644 index 0000000..7d80566 --- /dev/null +++ b/tfe/terraform.tf @@ -0,0 +1,19 @@ +terraform { + required_version = ">= 1.6" + + required_providers { + nutanix = { + source = "nutanix/nutanix" + version = "~> 2.4" # v3-API-based resources still ship in current provider + } + } + + # CLI-driven workspace: state + execution in HCP, runs dispatch to your + # in-network agent. `terraform login` once on this machine first. + cloud { + organization = "homelab_nutanix" + workspaces { + name = "hl-dev" + } + } +} diff --git a/tfe/variables.tf b/tfe/variables.tf new file mode 100644 index 0000000..dc968d1 --- /dev/null +++ b/tfe/variables.tf @@ -0,0 +1,28 @@ +variable "vm_name" { + type = string + default = "tf-lab-smoke-01" +} + +variable "cluster_name" { + type = string + description = "PE cluster name as shown in Prism (you named it cluster1)." + default = "cluster1" +} + +variable "subnet_name" { + type = string + description = "Name of the AHV subnet/network to attach the NIC to." + # No default — set in terraform.tfvars or a workspace variable. +} + +variable "memory_mib" { + type = number + description = "VM memory in MiB." + default = 2048 # 2 GiB +} + +variable "disk_mib" { + type = number + description = "Boot disk size in MiB." + default = 20480 # 20 GiB +} From 1e18337147de2a8b8a112875dd359f2b232dee06 Mon Sep 17 00:00:00 2001 From: Richard S Gardner <8586577+rgardner4012@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:02:33 -0400 Subject: [PATCH 2/2] Switch to one vs hard index [0] Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tfe/main.tf | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tfe/main.tf b/tfe/main.tf index 86533a8..92b51e5 100644 --- a/tfe/main.tf +++ b/tfe/main.tf @@ -12,11 +12,10 @@ locals { # Build a name->uuid map of clusters registered to PC, then pick ours by name. # On single-node CE there's one PE cluster; var.cluster_name must match what # PE/PC shows (you named it "cluster1"). - cluster_uuid = [ - for c in data.nutanix_clusters.clusters.entities : - c.metadata.uuid + cluster_uuid = one([ + for c in data.nutanix_clusters.clusters.entities : c.metadata.uuid if c.name == var.cluster_name - ][0] + ]) } # Look up the subnet by name -> returns .id (subnet UUID).