diff --git a/builder/nutanix/config.go b/builder/nutanix/config.go index c5214b7..40961b1 100644 --- a/builder/nutanix/config.go +++ b/builder/nutanix/config.go @@ -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 { diff --git a/builder/nutanix/config.hcl2spec.go b/builder/nutanix/config.hcl2spec.go index 4743750..75de288 100644 --- a/builder/nutanix/config.hcl2spec.go +++ b/builder/nutanix/config.hcl2spec.go @@ -308,11 +308,13 @@ func (*FlatVmConfig) HCL2Spec() map[string]hcldec.Spec { // FlatVmDisk is an auto-generated flat version of VmDisk. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatVmDisk struct { - ImageType *string `mapstructure:"image_type" json:"image_type" required:"false" cty:"image_type" hcl:"image_type"` - SourceImageName *string `mapstructure:"source_image_name" json:"source_image_name" required:"false" cty:"source_image_name" hcl:"source_image_name"` - SourceImageUUID *string `mapstructure:"source_image_uuid" json:"source_image_uuid" required:"false" cty:"source_image_uuid" hcl:"source_image_uuid"` - SourceImageURI *string `mapstructure:"source_image_uri" json:"source_image_uri" required:"false" cty:"source_image_uri" hcl:"source_image_uri"` - DiskSizeGB *int64 `mapstructure:"disk_size_gb" json:"disk_size_gb" required:"false" cty:"disk_size_gb" hcl:"disk_size_gb"` + ImageType *string `mapstructure:"image_type" json:"image_type" required:"false" cty:"image_type" hcl:"image_type"` + SourceImageName *string `mapstructure:"source_image_name" json:"source_image_name" required:"false" cty:"source_image_name" hcl:"source_image_name"` + SourceImageUUID *string `mapstructure:"source_image_uuid" json:"source_image_uuid" required:"false" cty:"source_image_uuid" hcl:"source_image_uuid"` + SourceImageURI *string `mapstructure:"source_image_uri" json:"source_image_uri" required:"false" cty:"source_image_uri" hcl:"source_image_uri"` + SourceImageDelete *bool `mapstructure:"source_image_delete" json:"source_image_delete" required:"false" cty:"source_image_delete" hcl:"source_image_delete"` + SourceImageForce *bool `mapstructure:"source_image_force" json:"source_image_force" required:"false" cty:"source_image_force" hcl:"source_image_force"` + DiskSizeGB *int64 `mapstructure:"disk_size_gb" json:"disk_size_gb" required:"false" cty:"disk_size_gb" hcl:"disk_size_gb"` } // FlatMapstructure returns a new FlatVmDisk. @@ -327,11 +329,13 @@ func (*VmDisk) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } // The decoded values from this spec will then be applied to a FlatVmDisk. func (*FlatVmDisk) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ - "image_type": &hcldec.AttrSpec{Name: "image_type", Type: cty.String, Required: false}, - "source_image_name": &hcldec.AttrSpec{Name: "source_image_name", Type: cty.String, Required: false}, - "source_image_uuid": &hcldec.AttrSpec{Name: "source_image_uuid", Type: cty.String, Required: false}, - "source_image_uri": &hcldec.AttrSpec{Name: "source_image_uri", Type: cty.String, Required: false}, - "disk_size_gb": &hcldec.AttrSpec{Name: "disk_size_gb", Type: cty.Number, Required: false}, + "image_type": &hcldec.AttrSpec{Name: "image_type", Type: cty.String, Required: false}, + "source_image_name": &hcldec.AttrSpec{Name: "source_image_name", Type: cty.String, Required: false}, + "source_image_uuid": &hcldec.AttrSpec{Name: "source_image_uuid", Type: cty.String, Required: false}, + "source_image_uri": &hcldec.AttrSpec{Name: "source_image_uri", Type: cty.String, Required: false}, + "source_image_delete": &hcldec.AttrSpec{Name: "source_image_delete", Type: cty.Bool, Required: false}, + "source_image_force": &hcldec.AttrSpec{Name: "source_image_force", Type: cty.Bool, Required: false}, + "disk_size_gb": &hcldec.AttrSpec{Name: "disk_size_gb", Type: cty.Number, Required: false}, } return s } diff --git a/builder/nutanix/driver.go b/builder/nutanix/driver.go index 06283e7..012d366 100644 --- a/builder/nutanix/driver.go +++ b/builder/nutanix/driver.go @@ -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" ) @@ -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) @@ -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), @@ -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 == "" { @@ -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 != "" { @@ -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 != "" { @@ -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{} @@ -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, @@ -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 != "" { @@ -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), @@ -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) @@ -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 } diff --git a/builder/nutanix/step_build_vm.go b/builder/nutanix/step_build_vm.go index f558348..f3b6915 100644 --- a/builder/nutanix/step_build_vm.go +++ b/builder/nutanix/step_build_vm.go @@ -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) @@ -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) @@ -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) + } + } diff --git a/docs/builders/nutanix.mdx b/docs/builders/nutanix.mdx index d9221e1..0b72c6b 100644 --- a/docs/builders/nutanix.mdx +++ b/docs/builders/nutanix.mdx @@ -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: @@ -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