Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement category mapping for image / error handling improvement #99

Merged
merged 1 commit into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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