Skip to content

Commit

Permalink
implement category mapping for image
Browse files Browse the repository at this point in the history
implement common checkTask validation
cleaning messaging
  • Loading branch information
tuxtof committed Feb 13, 2023
1 parent 1e79c93 commit 014ba7b
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 63 deletions.
13 changes: 13 additions & 0 deletions builder/nutanix/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ type Config struct {
VmConfig `mapstructure:",squash"`
ForceDeregister bool `mapstructure:"force_deregister" json:"force_deregister" required:"false"`
ImageDescription string `mapstructure:"image_description" json:"image_description" required:"false"`
ImageCategoryKey string `mapstructure:"image_category_key" json:"image_category_key" required:"false"`
ImageCategoryValue string `mapstructure:"image_category_value" json:"image_category_value" required:"false"`
WaitTimeout time.Duration `mapstructure:"ip_wait_timeout" json:"ip_wait_timeout" required:"false"`

ctx interpolate.Context
Expand Down Expand Up @@ -150,6 +152,17 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
c.VmConfig.ImageName = c.VmConfig.VMName
}

// Validate if both Image Category key and value are given in same time
if c.ImageCategoryKey != "" && c.ImageCategoryValue == "" {
log.Println("Nutanix Image Category value missing from configuration")
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("missing image_category_value"))
}

if c.ImageCategoryKey == "" && c.ImageCategoryValue != "" {
log.Println("Nutanix Image Category key missing from configuration")
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("missing image_category_key"))
}

if c.CommConfig.SSHPort == 0 {
log.Println("SSHPort not set, defaulting to 22")
c.CommConfig.SSHPort = 22
Expand Down
4 changes: 4 additions & 0 deletions builder/nutanix/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 60 additions & 51 deletions builder/nutanix/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type Driver interface {
UploadImage(string, string, string, VmConfig) (*nutanixImage, error)
DeleteImage(string) error
GetImage(string) (*nutanixImage, error)
SaveVMDisk(string) (*nutanixImage, error)
SaveVMDisk(string, string, string) (*nutanixImage, error)
WaitForShutdown(string, <-chan struct{}) bool
}

Expand Down Expand Up @@ -171,6 +171,29 @@ func findImageByName(conn *v3.Client, name string) (*v3.ImageIntentResponse, err
return findImageByUUID(conn, *found[0].Metadata.UUID)
}

func checkTask(conn *v3.Client, taskUUID string) error {

log.Printf("checking task %s...", taskUUID)
var task *v3.TasksResponse
var err error
for i := 0; i < 120; i++ {
task, err = conn.V3.GetTask(taskUUID)
if err == nil {
if *task.Status == "SUCCEEDED" {
return nil
} else if *task.Status == "FAILED" {
return fmt.Errorf(*task.ErrorDetail)
} else {
log.Printf("current task status is: " + *task.Status)
<-time.After(5 * time.Second)
}
} else {
return err
}
}
return fmt.Errorf("check task %s timeout", taskUUID)
}

func (d *nutanixInstance) Addresses() []string {
var addresses []string
if len(d.nutanix.Status.Resources.NicList) > 0 {
Expand Down Expand Up @@ -422,47 +445,27 @@ func (d *NutanixDriver) Create(req *v3.VMIntentInput) (*nutanixInstance, error)

resp, err := conn.V3.CreateVM(req)
if err != nil {
log.Printf("Error creating vm: [%v]", err)
log.Printf("error creating vm: %s", err.Error())
return nil, err
}

uuid := *resp.Metadata.UUID
taskUUID := resp.Status.ExecutionContext.TaskUUID.(string)

log.Printf("waiting for vm (%s) to create: %s", uuid, taskUUID)
log.Printf("creating vm %s...", uuid)

var vm *v3.VMIntentResponse
for {
vm, err = conn.V3.GetVM(uuid)
if err == nil {
if *vm.Status.State == "COMPLETE" {
log.Printf("vm created successfully: " + *vm.Status.State)
break
} else if *vm.Status.State == "ERROR" {
var errTxt string
for i := 0; i < len(vm.Status.MessageList); i++ {
errTxt = *(vm.Status.MessageList)[i].Message
log.Printf("Nutanix Error Message: %s", *(vm.Status.MessageList)[i].Message)
log.Printf("Nutanix Error Reason: %s", *(vm.Status.MessageList)[i].Reason)
log.Printf("Nutanix Error Details: %s", (vm.Status.MessageList)[i].Details)
}
return nil, fmt.Errorf(errTxt)
} else {
log.Printf("Current status is: " + *vm.Status.State)
time.Sleep(5 * time.Second)
}
} else {
log.Printf("Error while getting VM Status, %s", err.Error())
return nil, err
}
err = checkTask(conn, resp.Status.ExecutionContext.TaskUUID.(string))

if err != nil {
log.Printf("error creating vm: %s", err.Error())
return nil, err
}

// Wait for the VM obtain an IP address

log.Printf("[INFO] Waiting for IP, up to timeout: %s", d.Config.WaitTimeout)

iteration := int(d.Config.WaitTimeout.Seconds()) / 5

var vm *v3.VMIntentResponse
for i := 0; i < iteration; i++ {
vm, err = conn.V3.GetVM(uuid)
if err != nil || len(vm.Status.Resources.NicList[0].IPEndpointList) == (0) {
Expand Down Expand Up @@ -570,7 +573,7 @@ func (d *NutanixDriver) UploadImage(imagePath string, sourceType string, imageTy
if *running.Status.State == "COMPLETE" {
break
}
time.Sleep(5 * time.Second)
<-time.After(5 * time.Second)
}

if sourceType == "PATH" {
Expand All @@ -586,7 +589,7 @@ func (d *NutanixDriver) UploadImage(imagePath string, sourceType string, imageTy
if *running.Status.State == "COMPLETE" {
break
}
time.Sleep(5 * time.Second)
<-time.After(5 * time.Second)
}
}
return &nutanixImage{image: *image}, nil
Expand Down Expand Up @@ -727,7 +730,7 @@ func (d *NutanixDriver) PowerOff(vmUUID string) error {
log.Printf("PowerOff task: %s", taskUUID)
return nil
}
func (d *NutanixDriver) SaveVMDisk(diskUUID string) (*nutanixImage, error) {
func (d *NutanixDriver) SaveVMDisk(diskUUID string, imageCategoryKey string, imageCategoryValue string) (*nutanixImage, error) {

configCreds := client.Credentials{
URL: fmt.Sprintf("%s:%d", d.ClusterConfig.Endpoint, d.ClusterConfig.Port),
Expand All @@ -751,29 +754,22 @@ func (d *NutanixDriver) SaveVMDisk(diskUUID string) (*nutanixImage, error) {
return nil, fmt.Errorf("error while ListAllImage, %s", err.Error())
}
if *ImageList.Metadata.TotalMatches == 0 {
log.Println("Image with given Name not found, no need to deregister")
log.Println("image with given Name not found, no need to deregister")
} else if *ImageList.Metadata.TotalMatches > 1 {
log.Println("More than one image with given Name found, will not deregister")
log.Println("more than one image with given Name found, will not deregister")
} else if *ImageList.Metadata.TotalMatches == 1 {
log.Println("Exactly one image with given Name found, will deregister")
log.Println("exactly one image with given Name found, will deregister")

resp, err := conn.V3.DeleteImage(*ImageList.Entities[0].Metadata.UUID)
if err != nil {
return nil, fmt.Errorf("error while DeleteImage, %s", err.Error())
return nil, fmt.Errorf("error while Delete Image, %s", err.Error())
}
taskUUID := resp.Status.ExecutionContext.TaskUUID.(string)
log.Printf("Wait until delete Image %s is finished, %s\n", *ImageList.Entities[0].Metadata.UUID, taskUUID)
// Wait for the Image to be deleted
for i := 0; i < 1200; i++ {
resp, err := conn.V3.GetTask(taskUUID)
if err != nil || *resp.Status != "SUCCEEDED" {
<-time.After(1 * time.Second)
continue
}
if *resp.Status == "SUCCEEDED" {
break
}
return nil, fmt.Errorf("error while Image Delete getting Task Status, %s", err.Error())

log.Printf("deleting image %s...\n", *ImageList.Entities[0].Metadata.UUID)
err = checkTask(conn, resp.Status.ExecutionContext.TaskUUID.(string))

if err != nil {
return nil, fmt.Errorf("error while Delete Image, %s", err.Error())
}
}
}
Expand All @@ -797,11 +793,24 @@ func (d *NutanixDriver) SaveVMDisk(diskUUID string) (*nutanixImage, error) {
},
}

if imageCategoryKey != "" && imageCategoryValue != "" {
c := make(map[string]string)
c[imageCategoryKey] = imageCategoryValue
req.Metadata.Categories = c
}

image, err := conn.V3.CreateImage(req)
if err != nil {
return nil, fmt.Errorf("error while CreateImage, %s", err.Error())
return nil, fmt.Errorf("error while Create Image, %s", err.Error())
}
return &nutanixImage{image: *image}, nil
log.Printf("creating image %s...\n", *image.Metadata.UUID)
err = checkTask(conn, image.Status.ExecutionContext.TaskUUID.(string))
if err != nil {
return nil, fmt.Errorf("error while Create Image, %s", err.Error())
} else {
return &nutanixImage{image: *image}, nil
}

}

func getEmptyClientSideFilter() []*client.AdditionalFilter {
Expand Down
11 changes: 7 additions & 4 deletions builder/nutanix/step_build_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nutanix

import (
"context"
"fmt"
"log"

"github.com/hashicorp/packer-plugin-sdk/multistep"
Expand Down Expand Up @@ -42,25 +43,27 @@ func (s *stepBuildVM) Run(ctx context.Context, state multistep.StateBag) multist
log.Println("no CD disk, not attaching.")
}

ui.Say("Creating Packer Builder VM on Nutanix Cluster...")
ui.Say("Creating Packer Builder virtual machine...")
//CreateRequest()
vmRequest, err := d.CreateRequest(config.VmConfig)
if err != nil {
ui.Error("Error creating Request: " + err.Error())
ui.Error("Error creating virtual machine request: " + err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
vmInstance, err := d.Create(vmRequest)

if err != nil {
ui.Error("Unable to create Nutanix VM request: " + err.Error())
ui.Error("Unable to create virtual machine: " + err.Error())
state.Put("error", err)
return multistep.ActionHalt
}

ui.Message(fmt.Sprintf("virtual machine %s created", config.VMName))
log.Printf("Nutanix VM UUID: %s", *vmInstance.nutanix.Metadata.UUID)
state.Put("vmUUID", *vmInstance.nutanix.Metadata.UUID)
state.Put("ip", vmInstance.Addresses()[0])
ui.Say("IP for Nutanix device: " + vmInstance.Addresses()[0])
ui.Message("Found IP for virtual machine: " + vmInstance.Addresses()[0])

return multistep.ActionContinue
}
Expand Down
11 changes: 5 additions & 6 deletions builder/nutanix/step_copy_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,19 @@ type stepCopyImage struct {
func (s *stepCopyImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
vmUUID := state.Get("vmUUID").(string)
ui.Say("Retrieving Status for uuid: " + vmUUID)
d := state.Get("driver").(Driver)
vm, _ := d.GetVM(vmUUID)

ui.Say("Creating image for uuid: " + vmUUID)
ui.Say(fmt.Sprintf("Creating image from virtual machine %s...", s.Config.VMName))

ui.Message("Initiatiating save VM DISK task.")
// Choose disk to replicate - looking for first "DISK"
var diskToCopy string

for i := range vm.nutanix.Spec.Resources.DiskList {
if *vm.nutanix.Spec.Resources.DiskList[i].DeviceProperties.DeviceType == "DISK" {
diskToCopy = *vm.nutanix.Spec.Resources.DiskList[i].UUID
ui.Message("Found DISK to copy: " + diskToCopy)
diskID := fmt.Sprintf("%s:%d", *vm.nutanix.Spec.Resources.DiskList[i].DeviceProperties.DiskAddress.AdapterType, *vm.nutanix.Spec.Resources.DiskList[i].DeviceProperties.DiskAddress.DeviceIndex)
ui.Message("Found disk to copy: " + diskID)
break
}
}
Expand All @@ -41,9 +40,9 @@ func (s *stepCopyImage) Run(ctx context.Context, state multistep.StateBag) multi
return multistep.ActionHalt
}

imageResponse, err := d.SaveVMDisk(diskToCopy)
imageResponse, err := d.SaveVMDisk(diskToCopy, s.Config.ImageCategoryKey, s.Config.ImageCategoryValue)
if err != nil {
ui.Error("Unexpected Nutanix Task status: " + err.Error())
ui.Error("Image creation failed: " + err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
Expand Down
4 changes: 2 additions & 2 deletions builder/nutanix/step_shutdown_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis

} else if s.Command != "" {
ui.Say("Gracefully halting virtual machine...")
log.Printf("Executing shutdown command: %s", s.Command)
log.Printf("executing shutdown command: %s", s.Command)
cmd := &packersdk.RemoteCmd{Command: s.Command}
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
err := fmt.Errorf("failed to send shutdown command: %s", err)
Expand All @@ -67,7 +67,7 @@ func (s *StepShutdown) Run(ctx context.Context, state multistep.StateBag) multis
}

// Wait for the machine to actually shut down
log.Printf("Waiting max %s for shutdown to complete", s.Timeout)
log.Printf("waiting max %s for shutdown to complete", s.Timeout)
shutdownTimer := time.After(s.Timeout)
for {
running, _ := driver.GetVM(vmUUID)
Expand Down
2 changes: 2 additions & 0 deletions docs/builders/nutanix.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ These parameters allow to configure everything around image creation, from the t
### All OS
- `image_name` (string) - Name of the output image.
- `image_description` (string) - Description for output image.
- `image_category_key` (string) - Name of the category to assign to the image.
- `image_category_value` (string) - Value of the category to assign to the image.
- `force_deregister` (bool) - Allow output image override if already exists.
- `shutdown_command` (string) - Command line to shutdown your temporary VM.
- `shutdown_timeout` (string) - Timeout for VM shutdown (format : 2m).
Expand Down

0 comments on commit 014ba7b

Please sign in to comment.