From 17c58e8d243fbac79c28260a0174fbf2187bd9d0 Mon Sep 17 00:00:00 2001 From: Heinrich Kruger Date: Thu, 26 May 2022 16:21:49 +0100 Subject: [PATCH] Enable xva builder to create VM from existing template --- builder/xenserver/xva/builder.go | 10 +- .../xva/step_create_from_template.go | 191 ++++++++++++++++++ builder/xenserver/xva/step_import_instance.go | 6 + 3 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 builder/xenserver/xva/step_create_from_template.go diff --git a/builder/xenserver/xva/builder.go b/builder/xenserver/xva/builder.go index 4eee1c4..f64cafc 100644 --- a/builder/xenserver/xva/builder.go +++ b/builder/xenserver/xva/builder.go @@ -3,7 +3,6 @@ package xva import ( "context" "errors" - "fmt" "time" "github.com/hashicorp/hcl/v2/hcldec" @@ -74,8 +73,12 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, warns []stri // Validation - if self.config.SourcePath == "" { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("A source_path must be specified")) + if self.config.SourcePath == "" && self.config.CloneTemplate == "" { + errs = packer.MultiErrorAppend( + errs, errors.New("Either source_path or clone_template must be specified")) + } else if self.config.SourcePath != "" && self.config.CloneTemplate != "" { + errs = packer.MultiErrorAppend( + errs, errors.New("Only one of source_path and clone_template must be specified")) } if len(errs.Errors) > 0 { @@ -134,6 +137,7 @@ func (self *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (p VdiName: self.config.ToolsIsoName, VdiUuidKey: "tools_vdi_uuid", }, + new(stepCreateFromTemplate), new(stepImportInstance), &xscommon.StepAttachVdi{ VdiUuidKey: "floppy_vdi_uuid", diff --git a/builder/xenserver/xva/step_create_from_template.go b/builder/xenserver/xva/step_create_from_template.go new file mode 100644 index 0000000..a4bd436 --- /dev/null +++ b/builder/xenserver/xva/step_create_from_template.go @@ -0,0 +1,191 @@ +package xva + +import ( + "context" + "fmt" + "log" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" + xsclient "github.com/terra-farm/go-xen-api-client" + xscommon "github.com/xenserver/packer-builder-xenserver/builder/xenserver/common" +) + +type stepCreateFromTemplate struct { + instance *xsclient.VMRef +} + +func (self *stepCreateFromTemplate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + + c := state.Get("client").(*xscommon.Connection) + config := state.Get("config").(xscommon.Config) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Step: Create Instance from Template") + + if config.CloneTemplate == "" { + log.Println("Skipping creation from template - no `clone_template` configured.") + return multistep.ActionContinue + } + + // Get the template to clone from + + vms, err := c.GetClient().VM.GetByNameLabel(c.GetSessionRef(), config.CloneTemplate) + + switch { + case len(vms) == 0: + ui.Error(fmt.Sprintf("Couldn't find a template with the name-label '%s'. Aborting.", config.CloneTemplate)) + return multistep.ActionHalt + case len(vms) > 1: + ui.Error(fmt.Sprintf("Found more than one template with the name '%s'. The name must be unique. Aborting.", config.CloneTemplate)) + return multistep.ActionHalt + } + + template := vms[0] + + // Clone that VM template + instance, err := c.GetClient().VM.Clone(c.GetSessionRef(), template, config.VMName) + if err != nil { + ui.Error(fmt.Sprintf("Error cloning VM: %s", err.Error())) + return multistep.ActionHalt + } + self.instance = &instance + + err = c.GetClient().VM.SetIsATemplate(c.GetSessionRef(), instance, false) + if err != nil { + ui.Error(fmt.Sprintf("Error setting is_a_template=false: %s", err.Error())) + return multistep.ActionHalt + } + + err = c.GetClient().VM.SetVCPUsMax(c.GetSessionRef(), instance, int(config.VCPUsMax)) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM VCPUs Max=%d: %s", config.VCPUsMax, err.Error())) + return multistep.ActionHalt + } + + err = c.GetClient().VM.SetVCPUsAtStartup(c.GetSessionRef(), instance, int(config.VCPUsAtStartup)) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM VCPUs At Startup=%d: %s", config.VCPUsAtStartup, err.Error())) + return multistep.ActionHalt + } + + memory := int(config.VMMemory * 1024 * 1024) + err = c.GetClient().VM.SetMemoryLimits(c.GetSessionRef(), instance, memory, memory, memory, memory) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM memory=%d: %s", memory, err.Error())) + return multistep.ActionHalt + } + + err = c.GetClient().VM.SetPlatform(c.GetSessionRef(), instance, config.PlatformArgs) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM platform: %s", err.Error())) + return multistep.ActionHalt + } + + err = c.GetClient().VM.SetNameDescription(c.GetSessionRef(), instance, config.VMDescription) + if err != nil { + ui.Error(fmt.Sprintf("Error setting VM description: %s", err.Error())) + return multistep.ActionHalt + } + + // Connect Network + + var network xsclient.NetworkRef + + if len(config.NetworkNames) == 0 { + // No network has be specified. Use the management interface + log.Println("No network name given, attempting to use management interface") + pifs, err := c.GetClient().PIF.GetAll(c.GetSessionRef()) + + if err != nil { + ui.Error(fmt.Sprintf("Error getting PIFs: %s", err.Error())) + return multistep.ActionHalt + } + + for _, pif := range pifs { + pif_rec, err := c.GetClient().PIF.GetRecord(c.GetSessionRef(), pif) + + if err != nil { + ui.Error(fmt.Sprintf("Error getting PIF record: %s", err.Error())) + return multistep.ActionHalt + } + + if pif_rec.Management { + network = pif_rec.Network + } + + } + + if string(network) == "" { + ui.Error("Error: couldn't find management network. Aborting.") + return multistep.ActionHalt + } + + log.Printf("Creating VIF on network '%s' on VM '%s'\n", network, instance) + _, err = xscommon.ConnectNetwork(c, network, instance, "0") + + if err != nil { + ui.Error(fmt.Sprintf("Failed to create VIF with error: %v", err)) + return multistep.ActionHalt + } + + } else { + log.Printf("Using provided network names: %v\n", config.NetworkNames) + // Look up each network by it's name label + for i, networkNameLabel := range config.NetworkNames { + networks, err := c.GetClient().Network.GetByNameLabel(c.GetSessionRef(), networkNameLabel) + + if err != nil { + ui.Error(fmt.Sprintf("Error occured getting Network by name-label: %s", err.Error())) + return multistep.ActionHalt + } + + switch { + case len(networks) == 0: + ui.Error(fmt.Sprintf("Couldn't find a network with the specified name-label '%s'. Aborting.", networkNameLabel)) + return multistep.ActionHalt + case len(networks) > 1: + ui.Error(fmt.Sprintf("Found more than one network with the name '%s'. The name must be unique. Aborting.", networkNameLabel)) + return multistep.ActionHalt + } + + //we need the VIF index string + vifIndexString := fmt.Sprintf("%d", i) + _, err = xscommon.ConnectNetwork(c, networks[0], instance, vifIndexString) + + if err != nil { + ui.Say(fmt.Sprintf("Failed to connect VIF with error: %v", err.Error())) + } + } + } + + instanceId, err := c.GetClient().VM.GetUUID(c.GetSessionRef(), instance) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get VM UUID: %s", err.Error())) + return multistep.ActionHalt + } + + state.Put("instance_uuid", instanceId) + ui.Say(fmt.Sprintf("Created instance '%s'", instanceId)) + + return multistep.ActionContinue +} + +func (self *stepCreateFromTemplate) Cleanup(state multistep.StateBag) { + config := state.Get("config").(xscommon.Config) + if config.ShouldKeepVM(state) { + return + } + + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*xscommon.Connection) + + if self.instance != nil { + ui.Say("Destroying VM") + _ = c.GetClient().VM.HardShutdown(c.GetSessionRef(), *self.instance) // redundant, just in case + err := c.GetClient().VM.Destroy(c.GetSessionRef(), *self.instance) + if err != nil { + ui.Error(err.Error()) + } + } +} diff --git a/builder/xenserver/xva/step_import_instance.go b/builder/xenserver/xva/step_import_instance.go index 54215fa..ecc53a8 100644 --- a/builder/xenserver/xva/step_import_instance.go +++ b/builder/xenserver/xva/step_import_instance.go @@ -3,6 +3,7 @@ package xva import ( "context" "fmt" + "log" "os" "github.com/hashicorp/packer-plugin-sdk/multistep" @@ -24,6 +25,11 @@ func (self *stepImportInstance) Run(ctx context.Context, state multistep.StateBa ui.Say("Step: Import Instance") + if config.SourcePath == "" { + log.Println("Skipping imporing instance - no `source_path` configured.") + return multistep.ActionContinue + } + // find the SR srs, err := c.GetClient().SR.GetAll(c.GetSessionRef()) sr := srs[0]