Skip to content

Commit

Permalink
implement delete and force source_image option (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
tuxtof authored Aug 8, 2023
1 parent 8e7cb3b commit 3541b9d
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 42 deletions.
12 changes: 7 additions & 5 deletions builder/nutanix/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,13 @@ type ClusterConfig struct {
}

type VmDisk struct {
ImageType string `mapstructure:"image_type" json:"image_type" required:"false"`
SourceImageName string `mapstructure:"source_image_name" json:"source_image_name" required:"false"`
SourceImageUUID string `mapstructure:"source_image_uuid" json:"source_image_uuid" required:"false"`
SourceImageURI string `mapstructure:"source_image_uri" json:"source_image_uri" required:"false"`
DiskSizeGB int64 `mapstructure:"disk_size_gb" json:"disk_size_gb" required:"false"`
ImageType string `mapstructure:"image_type" json:"image_type" required:"false"`
SourceImageName string `mapstructure:"source_image_name" json:"source_image_name" required:"false"`
SourceImageUUID string `mapstructure:"source_image_uuid" json:"source_image_uuid" required:"false"`
SourceImageURI string `mapstructure:"source_image_uri" json:"source_image_uri" required:"false"`
SourceImageDelete bool `mapstructure:"source_image_delete" json:"source_image_delete" required:"false"`
SourceImageForce bool `mapstructure:"source_image_force" json:"source_image_force" required:"false"`
DiskSizeGB int64 `mapstructure:"disk_size_gb" json:"disk_size_gb" required:"false"`
}

type VmNIC struct {
Expand Down
24 changes: 14 additions & 10 deletions builder/nutanix/config.hcl2spec.go

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

135 changes: 110 additions & 25 deletions builder/nutanix/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"log"
"path"

"github.com/hashicorp/packer-plugin-sdk/multistep"
client "github.com/nutanix-cloud-native/prism-go-client/pkg/nutanix"
v3 "github.com/nutanix-cloud-native/prism-go-client/pkg/nutanix/v3"
)
Expand All @@ -24,13 +25,14 @@ const (
// A driver is able to talk to Nutanix PrismCentral and perform certain
// operations with it.
type Driver interface {
CreateRequest(VmConfig) (*v3.VMIntentInput, error)
CreateRequest(VmConfig, multistep.StateBag) (*v3.VMIntentInput, error)
Create(*v3.VMIntentInput) (*nutanixInstance, error)
Delete(string) error
GetVM(string) (*nutanixInstance, error)
GetHost(string) (*nutanixHost, error)
PowerOff(string) error
UploadImage(string, string, string, VmConfig) (*nutanixImage, error)
CreateImageURL(VmDisk, VmConfig) (*nutanixImage, error)
CreateImageFile(string, VmConfig) (*nutanixImage, error)
DeleteImage(string) error
GetImage(string) (*nutanixImage, error)
ExportImage(string) (io.ReadCloser, error)
Expand Down Expand Up @@ -257,7 +259,8 @@ func (d *NutanixDriver) WaitForShutdown(vmUUID string, cancelCh <-chan struct{})
return false
}
}
func (d *NutanixDriver) CreateRequest(vm VmConfig) (*v3.VMIntentInput, error) {

func (d *NutanixDriver) CreateRequest(vm VmConfig, state multistep.StateBag) (*v3.VMIntentInput, error) {

configCreds := client.Credentials{
URL: fmt.Sprintf("%s:%d", d.ClusterConfig.Endpoint, d.ClusterConfig.Port),
Expand All @@ -273,6 +276,8 @@ func (d *NutanixDriver) CreateRequest(vm VmConfig) (*v3.VMIntentInput, error) {
return nil, err
}

log.Printf("preparing vm %s...", d.Config.VMName)

// If UserData exists, create GuestCustomization
var guestCustomization *v3.GuestCustomization
if vm.UserData == "" {
Expand All @@ -298,14 +303,23 @@ func (d *NutanixDriver) CreateRequest(vm VmConfig) (*v3.VMIntentInput, error) {
DiskList := []*v3.VMDisk{}
SATAindex := 0
SCSIindex := 0

var imageToDelete []string

for _, disk := range vm.VmDisks {
if disk.ImageType == "DISK_IMAGE" {
image := &v3.ImageIntentResponse{}
if disk.SourceImageURI != "" {
image, err := d.UploadImage(disk.SourceImageURI, "URI", disk.ImageType, vm)
image, err := d.CreateImageURL(disk, vm)
if err != nil {
return nil, fmt.Errorf("error while findImageByUUID, Error %s", err.Error())
}

if disk.SourceImageDelete {
log.Printf("mark this image to delete: %s", *image.image.Status.Name)
imageToDelete = append(imageToDelete, *image.image.Metadata.UUID)
}

disk.SourceImageUUID = *image.image.Metadata.UUID
}
if disk.SourceImageUUID != "" {
Expand Down Expand Up @@ -362,10 +376,16 @@ func (d *NutanixDriver) CreateRequest(vm VmConfig) (*v3.VMIntentInput, error) {
if disk.ImageType == "ISO_IMAGE" {
image := &v3.ImageIntentResponse{}
if disk.SourceImageURI != "" {
image, err := d.UploadImage(disk.SourceImageURI, "URI", disk.ImageType, vm)
image, err := d.CreateImageURL(disk, vm)
if err != nil {
return nil, fmt.Errorf("error while findImageByUUID, Error %s", err.Error())
}

if disk.SourceImageDelete {
log.Printf("mark this image to delete %s:", *image.image.Status.Name)
imageToDelete = append(imageToDelete, *image.image.Metadata.UUID)
}

disk.SourceImageUUID = *image.image.Metadata.UUID
}
if disk.SourceImageUUID != "" {
Expand Down Expand Up @@ -397,6 +417,8 @@ func (d *NutanixDriver) CreateRequest(vm VmConfig) (*v3.VMIntentInput, error) {
}
}

state.Put("image_to_delete", imageToDelete)

NICList := []*v3.VMNic{}
for _, nic := range vm.VmNICs {
subnet := &v3.SubnetIntentResponse{}
Expand Down Expand Up @@ -563,8 +585,8 @@ func (d *NutanixDriver) Delete(vmUUID string) error {
return nil
}

// UploadImage (string, VmConfig) (*nutanixImage, error)
func (d *NutanixDriver) UploadImage(imagePath string, sourceType string, imageType string, vm VmConfig) (*nutanixImage, error) {
// CreateImageURL (VmDisk, VmConfig) (*nutanixImage, error)
func (d *NutanixDriver) CreateImageURL(disk VmDisk, vm VmConfig) (*nutanixImage, error) {
configCreds := client.Credentials{
URL: fmt.Sprintf("%s:%d", d.ClusterConfig.Endpoint, d.ClusterConfig.Port),
Endpoint: d.ClusterConfig.Endpoint,
Expand All @@ -579,7 +601,7 @@ func (d *NutanixDriver) UploadImage(imagePath string, sourceType string, imageTy
return nil, err
}

_, file := path.Split(imagePath)
_, file := path.Split(disk.SourceImageURI)

cluster := &v3.ClusterIntentResponse{}
if vm.ClusterUUID != "" {
Expand All @@ -600,7 +622,7 @@ func (d *NutanixDriver) UploadImage(imagePath string, sourceType string, imageTy
Spec: &v3.Image{
Name: &file,
Resources: &v3.ImageResources{
ImageType: &imageType,
ImageType: &disk.ImageType,
InitialPlacementRefList: InitialPlacementRef,
},
Description: StringPtr(defaultImageDLDescription),
Expand All @@ -609,15 +631,79 @@ func (d *NutanixDriver) UploadImage(imagePath string, sourceType string, imageTy
Kind: StringPtr("image"),
},
}
if sourceType == "URI" {
image, err := sourceImageExists(conn, file, imagePath)

image, err := sourceImageExists(conn, file, disk.SourceImageURI)
if err != nil {
return nil, fmt.Errorf("error while checking if image exists, %s", err.Error())
}
if image != nil && !disk.SourceImageForce {
log.Printf("reuse existing image: %s", *image.Status.Name)
return &nutanixImage{image: *image}, nil
} else if image != nil && disk.SourceImageForce {
log.Printf("delete existing image: %s", *image.Status.Name)
d.DeleteImage(*image.Metadata.UUID)
}
req.Spec.Resources.SourceURI = &disk.SourceImageURI

log.Printf("creating image: %s", file)
image, err = conn.V3.CreateImage(req)
if err != nil {
return nil, fmt.Errorf("error while create image: %s", err.Error())
}

err = checkTask(conn, image.Status.ExecutionContext.TaskUUID.(string))
if err != nil {
return nil, fmt.Errorf("error while create image: %s", err.Error())
}

return &nutanixImage{image: *image}, nil
}

// CreateImageFile (VmDisk, VmConfig) (*nutanixImage, error)
func (d *NutanixDriver) CreateImageFile(filePath string, vm VmConfig) (*nutanixImage, error) {
configCreds := client.Credentials{
URL: fmt.Sprintf("%s:%d", d.ClusterConfig.Endpoint, d.ClusterConfig.Port),
Endpoint: d.ClusterConfig.Endpoint,
Username: d.ClusterConfig.Username,
Password: d.ClusterConfig.Password,
Port: string(d.ClusterConfig.Port),
Insecure: d.ClusterConfig.Insecure,
}

conn, err := v3.NewV3Client(configCreds)
if err != nil {
return nil, err
}

_, file := path.Split(filePath)

cluster := &v3.ClusterIntentResponse{}
if vm.ClusterUUID != "" {
cluster, err = conn.V3.GetCluster(vm.ClusterUUID)
if err != nil {
return nil, fmt.Errorf("error while checking if image exists, %s", err.Error())
return nil, fmt.Errorf("error while GetCluster, %s", err.Error())
}
if image != nil {
return &nutanixImage{image: *image}, nil
} else if vm.ClusterName != "" {
cluster, err = findClusterByName(conn, vm.ClusterName)
if err != nil {
return nil, fmt.Errorf("error while findClusterByName, %s", err.Error())
}
req.Spec.Resources.SourceURI = &imagePath
}

refvalue := BuildReferenceValue(*cluster.Metadata.UUID, "cluster")
InitialPlacementRef := []*v3.ReferenceValues{refvalue}
req := &v3.ImageIntentInput{
Spec: &v3.Image{
Name: &file,
Resources: &v3.ImageResources{
ImageType: StringPtr("ISO_IMAGE"),
InitialPlacementRefList: InitialPlacementRef,
},
Description: StringPtr(defaultImageDLDescription),
},
Metadata: &v3.Metadata{
Kind: StringPtr("image"),
},
}

log.Printf("creating image: %s", file)
Expand All @@ -631,18 +717,17 @@ func (d *NutanixDriver) UploadImage(imagePath string, sourceType string, imageTy
return nil, fmt.Errorf("error while create image: %s", err.Error())
}

if sourceType == "PATH" {
log.Printf("uploading image: %s", imagePath)
err = conn.V3.UploadImage(*image.Metadata.UUID, imagePath)
if err != nil {
return nil, fmt.Errorf("error while upload image: %s", err.Error())
}
log.Printf("uploading image: %s", filePath)
err = conn.V3.UploadImage(*image.Metadata.UUID, filePath)
if err != nil {
return nil, fmt.Errorf("error while upload image: %s", err.Error())
}

running, err := conn.V3.GetImage(*image.Metadata.UUID)
if err != nil || *running.Status.State != "COMPLETE" {
return nil, fmt.Errorf("error while upload image: %s", err.Error())
}
running, err := conn.V3.GetImage(*image.Metadata.UUID)
if err != nil || *running.Status.State != "COMPLETE" {
return nil, fmt.Errorf("error while upload image: %s", err.Error())
}

return &nutanixImage{image: *image}, nil

}
Expand Down
11 changes: 9 additions & 2 deletions builder/nutanix/step_build_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (s *stepBuildVM) Run(ctx context.Context, state multistep.StateBag) multist
ui.Say("Uploading CD disk...")
cdFilesPath := cdPathRaw.(string)
log.Println("CD disk found " + cdFilesPath)
cdfilesImage, err := d.UploadImage(cdFilesPath, "PATH", "ISO_IMAGE", config.VmConfig)
cdfilesImage, err := d.CreateImageFile(cdFilesPath, config.VmConfig)
if err != nil {
ui.Error("Error uploading CD disk:" + err.Error())
state.Put("error", err)
Expand All @@ -46,7 +46,7 @@ func (s *stepBuildVM) Run(ctx context.Context, state multistep.StateBag) multist
ui.Say("Creating Packer Builder virtual machine...")

// Create VM Spec
vmRequest, err := d.CreateRequest(config.VmConfig)
vmRequest, err := d.CreateRequest(config.VmConfig, state)
if err != nil {
ui.Error("Error creating virtual machine request: " + err.Error())
state.Put("error", err)
Expand Down Expand Up @@ -113,4 +113,11 @@ func (s *stepBuildVM) Cleanup(state multistep.StateBag) {
ui.Message("Virtual machine successfully deleted")
}

imageToDelete := state.Get("image_to_delete")

for _, image := range imageToDelete.([]string) {
log.Printf("delete marked image: %s", image)
d.DeleteImage(image)
}

}
4 changes: 4 additions & 0 deletions docs/builders/nutanix.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ Sample:
- `source_image_name` (string) - Name of the image used as disk source.
- `source_image_uuid` (string) - UUID of the image used as disk source.
- `source_image_uri` (string) - URI of the image used as disk source (if image is not already on the cluster, it will download and store it before launching output image creation process).
- `source_image_delete` (bool) - Delete source image once build process is completed (default is false).
- `source_image_force` (bool) - Always download and replace source image even if already exist (default is false).
- `disk_size_gb` (number) - size of the disk (in gigabytes).

Sample:
Expand All @@ -104,6 +106,8 @@ Sample:
- `image_type` (string) - "ISO_IMAGE".
- `source_image_name` (string) - Name of the ISO image to mount.
- `source_image_uuid` (string) - UUID of the ISO image to mount.
- `source_image_delete` (bool) - Delete source image once build process is completed (default is false).
- `source_image_force` (bool) - Always download and replace source image even if already exist (default is false).

Sample:
```hcl
Expand Down

0 comments on commit 3541b9d

Please sign in to comment.