diff --git a/.web-docs/components/builder/nutanix/README.md b/.web-docs/components/builder/nutanix/README.md index 3f70dc7..927478f 100644 --- a/.web-docs/components/builder/nutanix/README.md +++ b/.web-docs/components/builder/nutanix/README.md @@ -84,6 +84,7 @@ 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_checksum` (string) - Checksum SHA-256 of the image used as disk source (work only with `source_image_uri`). - `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). diff --git a/builder/nutanix/config.go b/builder/nutanix/config.go index 44270ca..024a07c 100644 --- a/builder/nutanix/config.go +++ b/builder/nutanix/config.go @@ -6,6 +6,7 @@ import ( //"errors" "fmt" "log" + "regexp" "time" "github.com/hashicorp/packer-plugin-sdk/common" @@ -68,13 +69,14 @@ 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"` - 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"` + 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"` + SourceImageChecksum string `mapstructure:"source_image_checksum" json:"source_image_checksum" 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 { @@ -237,6 +239,22 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { } } + // Validate each disk + for index, disk := range c.VmConfig.VmDisks { + + // Validate checksum only with uri + if disk.SourceImageChecksum != "" && disk.SourceImageURI == "" { + log.Printf("disk %d: Checksum work only with Source Image URI\n", index) + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("disk %d: source_image_checksum work only with source_image_uri", index)) + } + + // Validate checksum format + if disk.SourceImageChecksum != "" && !IsSHA256Checksum(disk.SourceImageChecksum) { + log.Printf("disk %d: Invalid checksum format", index) + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("disk %d: invalid checksum format", index)) + } + } + if c.CommConfig.SSHPort == 0 { log.Println("SSHPort not set, defaulting to 22") c.CommConfig.SSHPort = 22 @@ -262,3 +280,11 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { return warnings, nil } + +// IsSHA256Checksum validates if the input string is a valid SHA-256 checksum +func IsSHA256Checksum(input string) bool { + // Regular expression for a 64-character hexadecimal string + const sha256Regex = `^[a-fA-F0-9]{64}$` + match, _ := regexp.MatchString(sha256Regex, input) + return match +} diff --git a/builder/nutanix/config.hcl2spec.go b/builder/nutanix/config.hcl2spec.go index 8e96503..66189f3 100644 --- a/builder/nutanix/config.hcl2spec.go +++ b/builder/nutanix/config.hcl2spec.go @@ -339,13 +339,14 @@ 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"` - 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"` + 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"` + SourceImageChecksum *string `mapstructure:"source_image_checksum" json:"source_image_checksum" required:"false" cty:"source_image_checksum" hcl:"source_image_checksum"` + 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. @@ -360,13 +361,14 @@ 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}, - "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}, + "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_checksum": &hcldec.AttrSpec{Name: "source_image_checksum", 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 0c4a259..5b07f06 100644 --- a/builder/nutanix/driver.go +++ b/builder/nutanix/driver.go @@ -731,6 +731,14 @@ func (d *NutanixDriver) CreateImageURL(ctx context.Context, disk VmDisk, vm VmCo } req.Spec.Resources.SourceURI = &disk.SourceImageURI + if disk.SourceImageChecksum != "" { + + req.Spec.Resources.Checksum = &v3.Checksum{ + ChecksumValue: &disk.SourceImageChecksum, + ChecksumAlgorithm: StringPtr("SHA_256"), + } + } + image, err := conn.V3.CreateImage(ctx, req) if err != nil { return nil, fmt.Errorf("error while creating image: %s", err.Error()) diff --git a/docs/builders/nutanix.mdx b/docs/builders/nutanix.mdx index 0054e66..49af07f 100644 --- a/docs/builders/nutanix.mdx +++ b/docs/builders/nutanix.mdx @@ -93,6 +93,7 @@ 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_checksum` (string) - Checksum SHA-256 of the image used as disk source (work only with `source_image_uri`). - `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). diff --git a/example/README.md b/example/README.md index b4a99ec..3ecca75 100644 --- a/example/README.md +++ b/example/README.md @@ -1,16 +1,16 @@ ## Examples Validate Manifests: -packer validate . +`packer validate .` Creating CentOS from local Image and running Provisioner: -packer build -only nutanix.centos . +`packer build -only nutanix.centos .` Creating Ubuntu from Upstream Image and running Provisioner: -packer build -only nutanix.ubuntu . +`packer build -only nutanix.ubuntu .` Creating from ISO with Kickstart-File: -packer build -only nutanix.centos-kickstart . +`packer build -only nutanix.centos-kickstart .` Windows Image (ISO Boot, VirtIO Drivers, cd_files) -packer build -only nutanix.windows . +`packer build -only nutanix.windows .`