Skip to content

Commit

Permalink
add export feature (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
tuxtof authored May 2, 2023
1 parent 511a955 commit 68be61a
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 30 deletions.
9 changes: 8 additions & 1 deletion builder/nutanix/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
},
}

if b.config.ImageExport {
steps = append(steps, &stepExportImage{
VMName: b.config.VMName,
ImageName: b.config.VmConfig.ImageName,
})
}

b.runner = &multistep.BasicRunner{Steps: steps}
b.runner.Run(ctx, state)

Expand All @@ -85,7 +92,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
if imageUUID != nil {
artifact := &Artifact{
Name: b.config.ImageName,
UUID: imageUUID.([]string)[0],
UUID: imageUUID.([]imageArtefact)[0].uuid,
}
return artifact, nil
}
Expand Down
1 change: 1 addition & 0 deletions builder/nutanix/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type Config struct {
ImageDescription string `mapstructure:"image_description" json:"image_description" required:"false"`
ImageCategories []Category `mapstructure:"image_categories" required:"false"`
ImageDelete bool `mapstructure:"image_delete" json:"image_delete" required:"false"`
ImageExport bool `mapstructure:"image_export" json:"image_export" required:"false"`
WaitTimeout time.Duration `mapstructure:"ip_wait_timeout" json:"ip_wait_timeout" required:"false"`

ctx interpolate.Context
Expand Down
2 changes: 2 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.

31 changes: 30 additions & 1 deletion builder/nutanix/driver.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package nutanix

import (
"crypto/tls"
"io"
"net/http"
"strings"
"time"

Expand All @@ -25,12 +28,12 @@ type Driver interface {
Create(*v3.VMIntentInput) (*nutanixInstance, error)
Delete(string) error
GetVM(string) (*nutanixInstance, error)
//GetImage(string) (*nutanixImage, error)
GetHost(string) (*nutanixHost, error)
PowerOff(string) error
UploadImage(string, string, string, VmConfig) (*nutanixImage, error)
DeleteImage(string) error
GetImage(string) (*nutanixImage, error)
ExportImage(string) (io.ReadCloser, error)
SaveVMDisk(string, int, []Category) (*nutanixImage, error)
WaitForShutdown(string, <-chan struct{}) bool
}
Expand Down Expand Up @@ -666,6 +669,32 @@ func (d *NutanixDriver) GetVM(vmUUID string) (*nutanixInstance, error) {
return &nutanixInstance{nutanix: *vm}, nil
}

func (d *NutanixDriver) ExportImage(imageUUID string) (io.ReadCloser, error) {
customTransport := http.DefaultTransport.(*http.Transport).Clone()
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: d.ClusterConfig.Insecure}

client := &http.Client{Transport: customTransport}

url := fmt.Sprintf("https://%s:%d/api/nutanix/v3/images/%s/file", d.ClusterConfig.Endpoint, d.ClusterConfig.Port, imageUUID)

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(d.ClusterConfig.Username, d.ClusterConfig.Password)

resp, err := client.Do(req)
if err != nil {
return nil, err
}

if resp.StatusCode != 200 {
return nil, fmt.Errorf(resp.Status)
}

return resp.Body, nil
}

func (d *NutanixDriver) GetHost(hostUUID string) (*nutanixHost, error) {

configCreds := client.Credentials{
Expand Down
2 changes: 1 addition & 1 deletion builder/nutanix/step_build_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (s *stepBuildVM) Run(ctx context.Context, state multistep.StateBag) multist
}

ui.Say("Creating Packer Builder virtual machine...")
//CreateRequest()

vmRequest, err := d.CreateRequest(config.VmConfig)
if err != nil {
ui.Error("Error creating virtual machine request: " + err.Error())
Expand Down
34 changes: 26 additions & 8 deletions builder/nutanix/step_copy_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ import (
"github.com/hashicorp/packer-plugin-sdk/packer"
)

type imageArtefact struct {
uuid string
size int64
}

type diskArtefact struct {
uuid string
size int64
}

type stepCopyImage struct {
Config *Config
}
Expand All @@ -22,11 +32,14 @@ func (s *stepCopyImage) Run(ctx context.Context, state multistep.StateBag) multi
ui.Say(fmt.Sprintf("Creating image(s) from virtual machine %s...", s.Config.VMName))

// Choose disk to replicate - looking for first "DISK"
var disksToCopy []string
var disksToCopy []diskArtefact

for i := range vm.nutanix.Spec.Resources.DiskList {
if *vm.nutanix.Spec.Resources.DiskList[i].DeviceProperties.DeviceType == "DISK" {
disksToCopy = append(disksToCopy, *vm.nutanix.Spec.Resources.DiskList[i].UUID)
disksToCopy = append(disksToCopy, diskArtefact{
uuid: *vm.nutanix.Spec.Resources.DiskList[i].UUID,
size: *vm.nutanix.Spec.Resources.DiskList[i].DiskSizeBytes,
})
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)
}
Expand All @@ -39,17 +52,22 @@ func (s *stepCopyImage) Run(ctx context.Context, state multistep.StateBag) multi
return multistep.ActionHalt
}

var imageList []string
var imageList []imageArtefact

for i, diskToCopy := range disksToCopy {

imageResponse, err := d.SaveVMDisk(diskToCopy, i, s.Config.ImageCategories)
imageResponse, err := d.SaveVMDisk(diskToCopy.uuid, i, s.Config.ImageCategories)
if err != nil {
ui.Error("Image creation failed: " + err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
imageList = append(imageList, *imageResponse.image.Metadata.UUID)

imageList = append(imageList, imageArtefact{
uuid: *imageResponse.image.Metadata.UUID,
size: diskToCopy.size,
})

ui.Message(fmt.Sprintf("Image successfully created: %s (%s)", *imageResponse.image.Spec.Name, *imageResponse.image.Metadata.UUID))
}

Expand All @@ -68,14 +86,14 @@ func (s *stepCopyImage) Cleanup(state multistep.StateBag) {
if imgUUID, ok := state.GetOk("image_uuid"); ok {
ui.Say(fmt.Sprintf("Deleting image(s) %s...", s.Config.ImageName))

for _, image := range imgUUID.([]string) {
for _, image := range imgUUID.([]imageArtefact) {

err := d.DeleteImage(image)
err := d.DeleteImage(image.uuid)
if err != nil {
ui.Error("An error occurred while deleting image")
return
} else {
ui.Message(fmt.Sprintf("Image successfully deleted (%s)", image))
ui.Message(fmt.Sprintf("Image successfully deleted (%s)", image.uuid))
}
}
}
Expand Down
101 changes: 101 additions & 0 deletions builder/nutanix/step_export_image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package nutanix

import (
"context"
"fmt"
"io"
"os"
"os/signal"
"syscall"

"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/packer"
)

type stepExportImage struct {
VMName string
ImageName string
}

func (s *stepExportImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
imageList := state.Get("image_uuid").([]imageArtefact)
d := state.Get("driver").(Driver)
// vm, _ := d.GetVM(vmUUID)

ui.Say(fmt.Sprintf("Exporting image(s) from virtual machine %s...", s.VMName))

// Create a channel to receive signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)

for index, imageToExport := range imageList {

name := s.ImageName
if index > 0 {
name = fmt.Sprintf("%s-disk%d", name, index+1)
}

file, err := d.ExportImage(imageToExport.uuid)
if err != nil {
ui.Error("Image export failed: " + err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
defer file.Close()

toRead := ui.TrackProgress(name, 0, imageToExport.size, file)

tempDestinationPath := name + ".tmp"

f, err := os.OpenFile(tempDestinationPath, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return multistep.ActionHalt
}

// Use a goroutine to copy the data, so that we can
// interrupt it if necessary
copyDone := make(chan bool)
go func() {
io.Copy(f, toRead)
copyDone <- true
}()

select {
case <-copyDone:
toRead.Close()

// Check if size is OK
fi, err := f.Stat()
if err != nil {
ui.Error("Image stat failed: " + err.Error())
state.Put("error", err)
return multistep.ActionHalt
}

if fi.Size() != imageToExport.size {
os.Remove(tempDestinationPath)
ui.Error("image size mistmatch")
state.Put("error", fmt.Errorf("image size mistmatch"))
return multistep.ActionHalt
}

name = name + ".img"
os.Rename(tempDestinationPath, name)

ui.Message(fmt.Sprintf("image %s exported", name))

case <-sigChan:
// We received a signal, cancel the copy operation
toRead.Close()
f.Close()
os.Remove(tempDestinationPath)
ui.Message("image export cancelled")
return multistep.ActionHalt
}

}
return multistep.ActionContinue
}

func (s *stepExportImage) Cleanup(state multistep.StateBag) {}
3 changes: 2 additions & 1 deletion docs/builders/nutanix.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ These parameters allow to configure everything around image creation, from the t
- `image_description` (string) - Description for output image.
- `image_categories` ([]Category) - Assign Categories to the image.
- `force_deregister` (bool) - Allow output image override if already exists.
- `image_delete` (bool) - Delete image once build process is completed.
- `image_delete` (bool) - Delete image once build process is completed (default is false).
- `image_export` (bool) - Export raw image in the current folder (default is false).
- `shutdown_command` (string) - Command line to shutdown your temporary VM.
- `shutdown_timeout` (string) - Timeout for VM shutdown (format : 2m).
- `communicator` (string) - Protocol used for Packer connection (ex "winrm" or "ssh"). Default is : "ssh".
Expand Down
12 changes: 6 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ replace (

require (
github.com/hashicorp/hcl/v2 v2.14.1
github.com/hashicorp/packer-plugin-sdk v0.3.2
github.com/hashicorp/packer-plugin-sdk v0.4.0
github.com/nutanix-cloud-native/prism-go-client v0.2.0
github.com/zclconf/go-cty v1.10.0
)
Expand Down Expand Up @@ -78,7 +78,7 @@ require (
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
github.com/masterzen/winrm v0.0.0-20210623064412-3b76017826b0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/go-fs v0.0.0-20180402235330-b7b9ca407fff // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
Expand All @@ -95,11 +95,11 @@ require (
github.com/ulikunitz/xz v0.5.10 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.101.0 // indirect
Expand Down
25 changes: 13 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/memberlist v0.2.4 h1:OOhYzSvFnkFQXm1ysE8RjXTHsqSRDyP4emusC9K7DYg=
github.com/hashicorp/memberlist v0.2.4/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/packer-plugin-sdk v0.3.2 h1:4Kqq7B8CRDMbfZmkloyz11t1hfqazJuBbW8ZFo4QlN4=
github.com/hashicorp/packer-plugin-sdk v0.3.2/go.mod h1:XZRvL9kRqJJtB6rf9Lu2zWLJbf2/4ImWXDjp9O9UQGE=
github.com/hashicorp/packer-plugin-sdk v0.4.0 h1:UyLYe0y02D9wkOQ3FeeZWyFg2+mx2vLuWRGUL5xt50I=
github.com/hashicorp/packer-plugin-sdk v0.4.0/go.mod h1:uNhU3pmjM2ejgHYce/g4J+sa5rh81iYQztpGvGa5FOs=
github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hashicorp/terraform-exec v0.16.1/go.mod h1:aj0lVshy8l+MHhFNoijNHtqTJQI3Xlowv5EOsEaGO7M=
Expand Down Expand Up @@ -367,8 +367,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
Expand Down Expand Up @@ -571,8 +571,8 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down Expand Up @@ -625,19 +625,20 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
Expand Down

0 comments on commit 68be61a

Please sign in to comment.