diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..2fa7d77 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml new file mode 100644 index 0000000..88c1220 --- /dev/null +++ b/.github/workflows/go-test.yml @@ -0,0 +1,24 @@ +name: go-test +on: + push: + branches: + - 'main' + pull_request: + +permissions: + contents: read + +jobs: + go-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Unshallow + run: git fetch --prune --unshallow + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + - name: Run go tests + run: go test -race -count 1 ./... -timeout=3m diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba31367..5f1061c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,16 +25,16 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.16 - name: Describe plugin id: plugin_describe run: echo "::set-output name=api_version::$(go run . describe | jq -r '.api_version')" - name: Import GPG key id: import_gpg - uses: hashicorp/ghaction-import-gpg@v2.1.0 - env: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + uses: crazy-max/ghaction-import-gpg@v5.0.0 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: diff --git a/README.md b/README.md index bd6511d..8e8d594 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ $ cp builder-xenserver-iso ~/.packer.d/plugins/packer-builder-xenserver-iso # Documentation For complete documentation on configuration commands, see [the -xenserver-iso docs](docs/builders/xenserver-iso.html.markdown) +xenserver-iso docs](docs/builders/iso/xenserver-iso.html.markdown) ## Support diff --git a/builder/xenserver/common/client.go b/builder/xenserver/common/client.go index 6341986..c3abff3 100644 --- a/builder/xenserver/common/client.go +++ b/builder/xenserver/common/client.go @@ -624,6 +624,17 @@ func ConnectNetwork(c *Connection, networkRef xenapi.NetworkRef, vmRef xenapi.VM return &vif, nil } +func AddVMTags(c *Connection, vmRef xenapi.VMRef, tags []string) error { + for _, tag := range tags { + log.Printf("Adding tag %s to VM %s\n", tag, vmRef) + err := c.GetClient().VM.AddTags(c.session, vmRef, tag) + if err != nil { + return err + } + } + return nil +} + // Setters func (self *VM) SetIsATemplate(is_a_template bool) (err error) { diff --git a/builder/xenserver/common/common_config.go b/builder/xenserver/common/common_config.go index 4c9f9e4..d36a830 100644 --- a/builder/xenserver/common/common_config.go +++ b/builder/xenserver/common/common_config.go @@ -20,10 +20,11 @@ type CommonConfig struct { VMName string `mapstructure:"vm_name"` VMDescription string `mapstructure:"vm_description"` SrName string `mapstructure:"sr_name"` - SrISOName string `mapstructure:"sr_iso_name"` + SrISOName string `mapstructure:"sr_iso_name" required:"false"` FloppyFiles []string `mapstructure:"floppy_files"` NetworkNames []string `mapstructure:"network_names"` ExportNetworkNames []string `mapstructure:"export_network_names"` + VMTags []string `mapstructure:"vm_tags"` HostPortMin uint `mapstructure:"host_port_min"` HostPortMax uint `mapstructure:"host_port_max"` @@ -225,29 +226,11 @@ func (c CommonConfig) ShouldKeepVM(state multistep.StateBag) bool { } func (config CommonConfig) GetSR(c *Connection) (xenapi.SRRef, error) { - var srRef xenapi.SRRef if config.SrName == "" { - hostRef, err := c.GetClient().Session.GetThisHost(c.session, c.session) - - if err != nil { - return srRef, err - } - - pools, err := c.GetClient().Pool.GetAllRecords(c.session) - - if err != nil { - return srRef, err - } - - for _, pool := range pools { - if pool.Master == hostRef { - return pool.DefaultSR, nil - } - } - - return srRef, errors.New(fmt.Sprintf("failed to find default SR on host '%s'", hostRef)) - + return getDefaultSR(c) } else { + var srRef xenapi.SRRef + // Use the provided name label to find the SR to use srs, err := c.GetClient().SR.GetByNameLabel(c.session, config.SrName) @@ -269,11 +252,11 @@ func (config CommonConfig) GetSR(c *Connection) (xenapi.SRRef, error) { func (config CommonConfig) GetISOSR(c *Connection) (xenapi.SRRef, error) { var srRef xenapi.SRRef if config.SrISOName == "" { - return srRef, errors.New("sr_iso_name must be specified in the packer configuration") + return getDefaultSR(c) } else { // Use the provided name label to find the SR to use - srs, err := c.GetClient().SR.GetByNameLabel(c.session, config.SrName) + srs, err := c.GetClient().SR.GetByNameLabel(c.session, config.SrISOName) if err != nil { return srRef, err @@ -281,11 +264,42 @@ func (config CommonConfig) GetISOSR(c *Connection) (xenapi.SRRef, error) { switch { case len(srs) == 0: - return srRef, fmt.Errorf("Couldn't find a SR with the specified name-label '%s'", config.SrName) + return srRef, fmt.Errorf("Couldn't find a SR with the specified name-label '%s'", config.SrISOName) case len(srs) > 1: - return srRef, fmt.Errorf("Found more than one SR with the name '%s'. The name must be unique", config.SrName) + return srRef, fmt.Errorf("Found more than one SR with the name '%s'. The name must be unique", config.SrISOName) } return srs[0], nil } } + +func getDefaultSR(c *Connection) (xenapi.SRRef, error) { + var srRef xenapi.SRRef + client := c.GetClient() + hostRef, err := client.Session.GetThisHost(c.session, c.session) + + if err != nil { + return srRef, err + } + + // The current version of the go-xen-api-client does not fully support XenAPI version 8.2 + // In particular, some values for the pool `allowed_operations` are not recognised, resulting + // in a parse error when retrieving pool records. As a workaround, we only fetch pool refs. + pool_refs, err := client.Pool.GetAll(c.session) + + if err != nil { + return srRef, err + } + + for _, pool_ref := range pool_refs { + pool_master, err := client.Pool.GetMaster(c.session, pool_ref) + if err != nil { + return srRef, err + } + if pool_master == hostRef { + return client.Pool.GetDefaultSR(c.session, pool_ref) + } + } + + return srRef, errors.New(fmt.Sprintf("failed to find default SR on host '%s'", hostRef)) +} diff --git a/builder/xenserver/common/config.go b/builder/xenserver/common/config.go index 53e76b7..67c7f18 100644 --- a/builder/xenserver/common/config.go +++ b/builder/xenserver/common/config.go @@ -1,4 +1,4 @@ -//go:generate mapstructure-to-hcl2 -type Config +//go:generate packer-sdc mapstructure-to-hcl2 -type Config package common import ( @@ -20,12 +20,12 @@ type Config struct { DiskSize uint `mapstructure:"disk_size"` CloneTemplate string `mapstructure:"clone_template"` VMOtherConfig map[string]string `mapstructure:"vm_other_config"` + VMTags []string `mapstructure:"vm_tags"` - ISOChecksum string `mapstructure:"iso_checksum"` - ISOChecksumType string `mapstructure:"iso_checksum_type"` - ISOUrls []string `mapstructure:"iso_urls"` - ISOUrl string `mapstructure:"iso_url"` - ISOName string `mapstructure:"iso_name"` + ISOChecksum string `mapstructure:"iso_checksum"` + ISOUrls []string `mapstructure:"iso_urls"` + ISOUrl string `mapstructure:"iso_url"` + ISOName string `mapstructure:"iso_name"` PlatformArgs map[string]string `mapstructure:"platform_args"` diff --git a/builder/xenserver/common/config.hcl2spec.go b/builder/xenserver/common/config.hcl2spec.go index 5e7780e..c917b7d 100644 --- a/builder/xenserver/common/config.hcl2spec.go +++ b/builder/xenserver/common/config.hcl2spec.go @@ -1,4 +1,4 @@ -// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT. +// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT. package common @@ -12,6 +12,7 @@ import ( type FlatConfig struct { PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` @@ -23,10 +24,11 @@ type FlatConfig struct { VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"` VMDescription *string `mapstructure:"vm_description" cty:"vm_description" hcl:"vm_description"` SrName *string `mapstructure:"sr_name" cty:"sr_name" hcl:"sr_name"` - SrISOName *string `mapstructure:"sr_iso_name" cty:"sr_iso_name" hcl:"sr_iso_name"` + SrISOName *string `mapstructure:"sr_iso_name" required:"false" cty:"sr_iso_name" hcl:"sr_iso_name"` FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"` NetworkNames []string `mapstructure:"network_names" cty:"network_names" hcl:"network_names"` ExportNetworkNames []string `mapstructure:"export_network_names" cty:"export_network_names" hcl:"export_network_names"` + VMTags []string `mapstructure:"vm_tags" cty:"vm_tags" hcl:"vm_tags"` HostPortMin *uint `mapstructure:"host_port_min" cty:"host_port_min" hcl:"host_port_min"` HostPortMax *uint `mapstructure:"host_port_max" cty:"host_port_max" hcl:"host_port_max"` BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` @@ -100,7 +102,6 @@ type FlatConfig struct { CloneTemplate *string `mapstructure:"clone_template" cty:"clone_template" hcl:"clone_template"` VMOtherConfig map[string]string `mapstructure:"vm_other_config" cty:"vm_other_config" hcl:"vm_other_config"` ISOChecksum *string `mapstructure:"iso_checksum" cty:"iso_checksum" hcl:"iso_checksum"` - ISOChecksumType *string `mapstructure:"iso_checksum_type" cty:"iso_checksum_type" hcl:"iso_checksum_type"` ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` ISOUrl *string `mapstructure:"iso_url" cty:"iso_url" hcl:"iso_url"` ISOName *string `mapstructure:"iso_name" cty:"iso_name" hcl:"iso_name"` @@ -125,6 +126,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false}, "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, @@ -140,6 +142,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false}, "network_names": &hcldec.AttrSpec{Name: "network_names", Type: cty.List(cty.String), Required: false}, "export_network_names": &hcldec.AttrSpec{Name: "export_network_names", Type: cty.List(cty.String), Required: false}, + "vm_tags": &hcldec.AttrSpec{Name: "vm_tags", Type: cty.List(cty.String), Required: false}, "host_port_min": &hcldec.AttrSpec{Name: "host_port_min", Type: cty.Number, Required: false}, "host_port_max": &hcldec.AttrSpec{Name: "host_port_max", Type: cty.Number, Required: false}, "boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false}, @@ -213,7 +216,6 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "clone_template": &hcldec.AttrSpec{Name: "clone_template", Type: cty.String, Required: false}, "vm_other_config": &hcldec.AttrSpec{Name: "vm_other_config", Type: cty.Map(cty.String), Required: false}, "iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false}, - "iso_checksum_type": &hcldec.AttrSpec{Name: "iso_checksum_type", Type: cty.String, Required: false}, "iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false}, "iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false}, "iso_name": &hcldec.AttrSpec{Name: "iso_name", Type: cty.String, Required: false}, diff --git a/builder/xenserver/iso/step_create_instance.go b/builder/xenserver/common/step_create_instance.go similarity index 76% rename from builder/xenserver/iso/step_create_instance.go rename to builder/xenserver/common/step_create_instance.go index 5c35268..54aede4 100644 --- a/builder/xenserver/iso/step_create_instance.go +++ b/builder/xenserver/common/step_create_instance.go @@ -1,4 +1,4 @@ -package iso +package common import ( "context" @@ -9,18 +9,21 @@ import ( "github.com/hashicorp/packer-plugin-sdk/packer" xenapi "github.com/terra-farm/go-xen-api-client" xsclient "github.com/terra-farm/go-xen-api-client" - xscommon "github.com/xenserver/packer-builder-xenserver/builder/xenserver/common" ) -type stepCreateInstance struct { +type StepCreateInstance struct { + // The XVA builder assumes it will boot an instance with an OS installed on its disks + // while the ISO builder needs packer to create a disk for an OS to be installed on. + AssumePreInstalledOS bool + instance *xsclient.VMRef vdi *xsclient.VDIRef } -func (self *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { +func (self *StepCreateInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - c := state.Get("client").(*xscommon.Connection) - config := state.Get("config").(xscommon.Config) + c := state.Get("client").(*Connection) + config := state.Get("config").(Config) ui := state.Get("ui").(packer.Ui) ui.Say("Step: Create Instance") @@ -101,37 +104,45 @@ func (self *stepCreateInstance) Run(ctx context.Context, state multistep.StateBa } } - // Create VDI for the instance - sr, err := config.GetSR(c) + if !self.AssumePreInstalledOS { + err = c.GetClient().VM.RemoveFromOtherConfig(c.GetSessionRef(), instance, "disks") + if err != nil { + ui.Error(fmt.Sprintf("Error removing disks from VM other-config: %s", err.Error())) + return multistep.ActionHalt + } - if err != nil { - ui.Error(fmt.Sprintf("Unable to get SR: %s", err.Error())) - return multistep.ActionHalt - } + // Create VDI for the instance + sr, err := config.GetSR(c) - ui.Say(fmt.Sprintf("Using the following SR for the VM: %s", sr)) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get SR: %s", err.Error())) + return multistep.ActionHalt + } - vdi, err := c.GetClient().VDI.Create(c.GetSessionRef(), xenapi.VDIRecord{ - NameLabel: "Packer-disk", - VirtualSize: int(config.DiskSize * 1024 * 1024), - Type: "user", - Sharable: false, - ReadOnly: false, - SR: sr, - OtherConfig: map[string]string{ - "temp": "temp", - }, - }) - if err != nil { - ui.Error(fmt.Sprintf("Unable to create packer disk VDI: %s", err.Error())) - return multistep.ActionHalt - } - self.vdi = &vdi + ui.Say(fmt.Sprintf("Using the following SR for the VM: %s", sr)) - err = xscommon.ConnectVdi(c, instance, vdi, xsclient.VbdTypeDisk) - if err != nil { - ui.Error(fmt.Sprintf("Unable to connect packer disk VDI: %s", err.Error())) - return multistep.ActionHalt + vdi, err := c.GetClient().VDI.Create(c.GetSessionRef(), xenapi.VDIRecord{ + NameLabel: "Packer-disk", + VirtualSize: int(config.DiskSize * 1024 * 1024), + Type: "user", + Sharable: false, + ReadOnly: false, + SR: sr, + OtherConfig: map[string]string{ + "temp": "temp", + }, + }) + if err != nil { + ui.Error(fmt.Sprintf("Unable to create packer disk VDI: %s", err.Error())) + return multistep.ActionHalt + } + self.vdi = &vdi + + err = ConnectVdi(c, instance, vdi, xsclient.VbdTypeDisk) + if err != nil { + ui.Error(fmt.Sprintf("Unable to connect packer disk VDI: %s", err.Error())) + return multistep.ActionHalt + } } // Connect Network @@ -168,7 +179,7 @@ func (self *stepCreateInstance) Run(ctx context.Context, state multistep.StateBa } log.Printf("Creating VIF on network '%s' on VM '%s'\n", network, instance) - _, err = xscommon.ConnectNetwork(c, network, instance, "0") + _, err = ConnectNetwork(c, network, instance, "0") if err != nil { ui.Error(fmt.Sprintf("Failed to create VIF with error: %v", err)) @@ -197,7 +208,7 @@ func (self *stepCreateInstance) Run(ctx context.Context, state multistep.StateBa //we need the VIF index string vifIndexString := fmt.Sprintf("%d", i) - _, err = xscommon.ConnectNetwork(c, networks[0], instance, vifIndexString) + _, err = ConnectNetwork(c, networks[0], instance, vifIndexString) if err != nil { ui.Say(fmt.Sprintf("Failed to connect VIF with error: %v", err.Error())) @@ -205,6 +216,12 @@ func (self *stepCreateInstance) Run(ctx context.Context, state multistep.StateBa } } + err = AddVMTags(c, instance, config.VMTags) + if err != nil { + ui.Error(fmt.Sprintf("Failed to add tags: %s", err.Error())) + return multistep.ActionHalt + } + instanceId, err := c.GetClient().VM.GetUUID(c.GetSessionRef(), instance) if err != nil { ui.Error(fmt.Sprintf("Unable to get VM UUID: %s", err.Error())) @@ -217,14 +234,14 @@ func (self *stepCreateInstance) Run(ctx context.Context, state multistep.StateBa return multistep.ActionContinue } -func (self *stepCreateInstance) Cleanup(state multistep.StateBag) { - config := state.Get("config").(xscommon.Config) +func (self *StepCreateInstance) Cleanup(state multistep.StateBag) { + config := state.Get("config").(Config) if config.ShouldKeepVM(state) { return } ui := state.Get("ui").(packer.Ui) - c := state.Get("client").(*xscommon.Connection) + c := state.Get("client").(*Connection) if self.instance != nil { ui.Say("Destroying VM") diff --git a/builder/xenserver/common/step_find_or_upload_vdi.go b/builder/xenserver/common/step_find_or_upload_vdi.go new file mode 100644 index 0000000..37b8453 --- /dev/null +++ b/builder/xenserver/common/step_find_or_upload_vdi.go @@ -0,0 +1,44 @@ +package common + +import ( + "context" + "fmt" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/packer" +) + +type StepFindOrUploadVdi struct { + StepUploadVdi +} + +func (self *StepFindOrUploadVdi) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + c := state.Get("client").(*Connection) + vdiName := self.VdiNameFunc() + + ui.Say(fmt.Sprintf("Attemping to find VDI '%s'", vdiName)) + + vdis, err := c.client.VDI.GetByNameLabel(c.session, vdiName) + if err != nil { + ui.Error(fmt.Sprintf("Failed to find VDI '%s' by name label: %s", vdiName, err.Error())) + return multistep.ActionHalt + } + + if len(vdis) > 1 { + ui.Error(fmt.Sprintf("Found more than one VDI with name '%s'. Name must be unique", vdiName)) + return multistep.ActionHalt + } else if len(vdis) == 1 { + + vdi := vdis[0] + + vdiUuid, err := c.client.VDI.GetUUID(c.session, vdi) + if err != nil { + ui.Error(fmt.Sprintf("Unable to get UUID of VDI '%s': %s", vdiName, err.Error())) + return multistep.ActionHalt + } + state.Put(self.VdiUuidKey, vdiUuid) + return multistep.ActionContinue + } + return self.uploadVdi(ctx, state) +} diff --git a/builder/xenserver/common/step_upload_vdi.go b/builder/xenserver/common/step_upload_vdi.go index 67a00f3..006e919 100644 --- a/builder/xenserver/common/step_upload_vdi.go +++ b/builder/xenserver/common/step_upload_vdi.go @@ -18,7 +18,7 @@ type StepUploadVdi struct { VdiUuidKey string } -func (self *StepUploadVdi) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { +func (self *StepUploadVdi) uploadVdi(ctx context.Context, state multistep.StateBag) multistep.StepAction { config := state.Get("commonconfig").(CommonConfig) ui := state.Get("ui").(packer.Ui) c := state.Get("client").(*Connection) @@ -33,10 +33,8 @@ func (self *StepUploadVdi) Run(ctx context.Context, state multistep.StateBag) mu ui.Say(fmt.Sprintf("Step: Upload VDI '%s'", vdiName)) // Create VDI for the image - srs, err := c.client.SR.GetAll(c.session) - ui.Say(fmt.Sprintf("Step: Found SRs '%v'", srs)) - sr, err := config.GetISOSR(c) + ui.Say(fmt.Sprintf("Step: Found SR for upload '%v'", sr)) if err != nil { ui.Error(fmt.Sprintf("Unable to get SR: %v", err)) @@ -96,6 +94,10 @@ func (self *StepUploadVdi) Run(ctx context.Context, state multistep.StateBag) mu return multistep.ActionContinue } +func (self *StepUploadVdi) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + return self.uploadVdi(ctx, state) +} + func (self *StepUploadVdi) Cleanup(state multistep.StateBag) { config := state.Get("commonconfig").(CommonConfig) ui := state.Get("ui").(packer.Ui) diff --git a/builder/xenserver/iso/builder.go b/builder/xenserver/iso/builder.go index e2fa601..8e8fe9a 100644 --- a/builder/xenserver/iso/builder.go +++ b/builder/xenserver/iso/builder.go @@ -40,7 +40,7 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, warns []stri }, raws...) if err != nil { - packer.MultiErrorAppend(errs, err) + errs = packer.MultiErrorAppend(errs, err) } errs = packer.MultiErrorAppend( @@ -95,12 +95,11 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, warns []stri // Template substitution templates := map[string]*string{ - "clone_template": &self.config.CloneTemplate, - "iso_checksum": &self.config.ISOChecksum, - "iso_checksum_type": &self.config.ISOChecksumType, - "iso_url": &self.config.ISOUrl, - "iso_name": &self.config.ISOName, - "install_timeout": &self.config.RawInstallTimeout, + "clone_template": &self.config.CloneTemplate, + "iso_checksum": &self.config.ISOChecksum, + "iso_url": &self.config.ISOUrl, + "iso_name": &self.config.ISOName, + "install_timeout": &self.config.RawInstallTimeout, } for i := range self.config.ISOUrls { templates[fmt.Sprintf("iso_urls[%d]", i)] = &self.config.ISOUrls[i] @@ -115,23 +114,8 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, warns []stri } if self.config.ISOName == "" { - // If ISO name is not specified, assume a URL and checksum has been provided. - - if self.config.ISOChecksumType == "" { - errs = packer.MultiErrorAppend( - errs, errors.New("The iso_checksum_type must be specified.")) - } else { - self.config.ISOChecksumType = strings.ToLower(self.config.ISOChecksumType) - if self.config.ISOChecksumType != "none" { - if self.config.ISOChecksum == "" { - errs = packer.MultiErrorAppend( - errs, errors.New("Due to the file size being large, an iso_checksum is required.")) - } else { - self.config.ISOChecksum = strings.ToLower(self.config.ISOChecksum) - } - } - } + self.config.ISOChecksum = strings.ToLower(self.config.ISOChecksum) if len(self.config.ISOUrls) == 0 { if self.config.ISOUrl == "" { @@ -140,10 +124,25 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, warns []stri } else { self.config.ISOUrls = []string{self.config.ISOUrl} } + } else if self.config.ISOUrl != "" { errs = packer.MultiErrorAppend( errs, errors.New("Only one of iso_url or iso_urls may be specified.")) } + + //The SDK can validate the ISO checksum and other sanity checks on the url. + iso_config := commonsteps.ISOConfig{ + ISOChecksum: self.config.ISOChecksum, + ISOUrls: self.config.ISOUrls, + } + + _, iso_errs := iso_config.Prepare(nil) + if iso_errs != nil { + for _, this_err := range iso_errs { + errs = packer.MultiErrorAppend(errs, this_err) + } + } + } else { // An ISO name has been provided. It should be attached from an available SR. @@ -187,7 +186,6 @@ func (self *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (p Url: self.config.ISOUrls, }, } - steps := []multistep.Step{ &xscommon.StepPrepareOutputDir{ Force: self.config.PackerForce, @@ -212,20 +210,22 @@ func (self *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (p }, VdiUuidKey: "floppy_vdi_uuid", }, - &xscommon.StepUploadVdi{ - VdiNameFunc: func() string { - if len(self.config.ISOUrls) > 0 { - return path.Base(self.config.ISOUrls[0]) - } - return "" + &xscommon.StepFindOrUploadVdi{ + xscommon.StepUploadVdi{ + VdiNameFunc: func() string { + if len(self.config.ISOUrls) > 0 { + return path.Base(self.config.ISOUrls[0]) + } + return "" + }, + ImagePathFunc: func() string { + if isoPath, ok := state.GetOk("iso_path"); ok { + return isoPath.(string) + } + return "" + }, + VdiUuidKey: "iso_vdi_uuid", }, - ImagePathFunc: func() string { - if isoPath, ok := state.GetOk("iso_path"); ok { - return isoPath.(string) - } - return "" - }, - VdiUuidKey: "iso_vdi_uuid", }, &xscommon.StepFindVdi{ VdiName: self.config.ToolsIsoName, @@ -235,7 +235,9 @@ func (self *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (p VdiName: self.config.ISOName, VdiUuidKey: "isoname_vdi_uuid", }, - new(stepCreateInstance), + &xscommon.StepCreateInstance{ + AssumePreInstalledOS: false, + }, &xscommon.StepAttachVdi{ VdiUuidKey: "floppy_vdi_uuid", VdiType: xsclient.VbdTypeFloppy, diff --git a/builder/xenserver/iso/builder_test.go b/builder/xenserver/iso/builder_test.go index dd4df15..c77d6dd 100644 --- a/builder/xenserver/iso/builder_test.go +++ b/builder/xenserver/iso/builder_test.go @@ -5,21 +5,22 @@ import ( "testing" "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/common" ) + func testConfig() map[string]interface{} { return map[string]interface{}{ "remote_host": "localhost", "remote_username": "admin", "remote_password": "admin", "vm_name": "foo", - "iso_checksum": "foo", - "iso_checksum_type": "md5", + "iso_checksum": "md5:A221725EE181A44C67E25BD6A2516742", "iso_url": "http://www.google.com/", "shutdown_command": "yes", "ssh_username": "foo", - packer.BuildNameConfigKey: "foo", + common.BuildNameConfigKey: "foo", } } @@ -179,9 +180,20 @@ func TestBuilderPrepare_ISOChecksum(t *testing.T) { var b Builder config := testConfig() + // Test good + + b = Builder{} + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + // Test bad config["iso_checksum"] = "" - _, warns, err := b.Prepare(config) + _, warns, err = b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) } @@ -189,80 +201,9 @@ func TestBuilderPrepare_ISOChecksum(t *testing.T) { t.Fatal("should have error") } - // Test good - config["iso_checksum"] = "FOo" - b = Builder{} - _, warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } - if b.config.ISOChecksum != "foo" { - t.Fatalf("should've lowercased: %s", b.config.ISOChecksum) - } } -func TestBuilderPrepare_ISOChecksumType(t *testing.T) { - var b Builder - config := testConfig() - - // Test bad - config["iso_checksum_type"] = "" - _, warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test good - config["iso_checksum_type"] = "mD5" - b = Builder{} - _, warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - if b.config.ISOChecksumType != "md5" { - t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) - } - - // Test unknown - config["iso_checksum_type"] = "fake" - b = Builder{} - _, warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test none - config["iso_checksum_type"] = "none" - b = Builder{} - _, warns, err = b.Prepare(config) - // @todo: give warning in this case? - /* - if len(warns) == 0 { - t.Fatalf("bad: %#v", warns) - } - */ - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - if b.config.ISOChecksumType != "none" { - t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) - } -} func TestBuilderPrepare_ISOUrl(t *testing.T) { var b Builder diff --git a/builder/xenserver/xva/builder.go b/builder/xenserver/xva/builder.go index cceeada..3a9bf1c 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" @@ -38,11 +37,12 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, warns []stri }, raws...) if err != nil { - packer.MultiErrorAppend(errs, err) + errs = packer.MultiErrorAppend(errs, err) } errs = packer.MultiErrorAppend( errs, self.config.CommonConfig.Prepare(self.config.GetInterpContext(), &self.config.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, self.config.SSHConfig.Prepare(self.config.GetInterpContext())...) // Set default values if self.config.VCPUsMax == 0 { @@ -74,8 +74,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 { @@ -101,7 +105,7 @@ func (self *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (p //Share state between the other steps using a statebag state := new(multistep.BasicStateBag) state.Put("client", c) - // state.Put("config", self.config) + state.Put("config", self.config) state.Put("commonconfig", self.config.CommonConfig) state.Put("hook", hook) state.Put("ui", ui) @@ -116,8 +120,11 @@ func (self *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (p }, &commonsteps.StepCreateFloppy{ Files: self.config.FloppyFiles, + Label: "cidata", + }, + &xscommon.StepHTTPServer{ + Chan: httpReqChan, }, - new(xscommon.StepHTTPServer), &xscommon.StepUploadVdi{ VdiNameFunc: func() string { return "Packer-floppy-disk" @@ -134,6 +141,9 @@ func (self *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (p VdiName: self.config.ToolsIsoName, VdiUuidKey: "tools_vdi_uuid", }, + &xscommon.StepCreateInstance{ + AssumePreInstalledOS: true, + }, new(stepImportInstance), &xscommon.StepAttachVdi{ VdiUuidKey: "floppy_vdi_uuid", @@ -153,20 +163,28 @@ func (self *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (p Chan: httpReqChan, Timeout: 300 * time.Minute, /*self.config.InstallTimeout*/ // @todo change this }, + &xscommon.StepForwardPortOverSSH{ + RemotePort: xscommon.InstanceSSHPort, + RemoteDest: xscommon.InstanceSSHIP, + HostPortMin: self.config.HostPortMin, + HostPortMax: self.config.HostPortMax, + ResultKey: "local_ssh_port", + }, &communicator.StepConnect{ Config: &self.config.SSHConfig.Comm, - Host: xscommon.CommHost, - SSHConfig: xscommon.SSHConfigFunc(self.config.CommonConfig.SSHConfig), - SSHPort: xscommon.SSHPort, + Host: xscommon.InstanceSSHIP, + SSHConfig: self.config.Comm.SSHConfigFunc(), + SSHPort: xscommon.InstanceSSHPort, }, new(commonsteps.StepProvision), new(xscommon.StepShutdown), - &xscommon.StepDetachVdi{ - VdiUuidKey: "floppy_vdi_uuid", - }, + new(xscommon.StepSetVmToTemplate), &xscommon.StepDetachVdi{ VdiUuidKey: "tools_vdi_uuid", }, + &xscommon.StepDetachVdi{ + VdiUuidKey: "floppy_vdi_uuid", + }, new(xscommon.StepDestroyVIFs), new(xscommon.StepExport), } diff --git a/builder/xenserver/xva/builder_test.go b/builder/xenserver/xva/builder_test.go index 03e029f..e5a5f96 100644 --- a/builder/xenserver/xva/builder_test.go +++ b/builder/xenserver/xva/builder_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/common" ) func testConfig() map[string]interface{} { @@ -16,7 +17,7 @@ func testConfig() map[string]interface{} { "ssh_username": "foo", "source_path": ".", - packer.BuildNameConfigKey: "foo", + common.BuildNameConfigKey: "foo", } } diff --git a/builder/xenserver/xva/step_import_instance.go b/builder/xenserver/xva/step_import_instance.go index 54215fa..d7c314e 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] @@ -80,6 +86,12 @@ func (self *stepImportInstance) Run(ctx context.Context, state multistep.StateBa return multistep.ActionHalt } + err = xscommon.AddVMTags(c, instance, config.VMTags) + if err != nil { + ui.Error(fmt.Sprintf("Failed to add tags: %s", err.Error())) + return multistep.ActionHalt + } + ui.Say(fmt.Sprintf("Imported instance '%s'", instanceId)) return multistep.ActionContinue diff --git a/docs/builders/iso/xenserver-iso.html.markdown b/docs/builders/iso/xenserver-iso.html.markdown index bfbb2c9..61464fd 100644 --- a/docs/builders/iso/xenserver-iso.html.markdown +++ b/docs/builders/iso/xenserver-iso.html.markdown @@ -29,21 +29,34 @@ each category, the available options are alphabetized and described. * `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO files are so large, this is required and Packer will verify it prior - to booting a virtual machine with the ISO attached. The type of the - checksum is specified with `iso_checksum_type`, documented below. + to booting a virtual machine with the ISO attached. The type of + the checksum is specified within the checksum field as a prefix, ex: + "md5:{$checksum}". The type of the checksum can also be omitted and + Packer will try to infer it based on string length. Valid values are + "none", "{$checksum}", "md5:{$checksum}", "sha1:{$checksum}", + "sha256:{$checksum}", "sha512:{$checksum}" or "file:{$path}". Here is a + list of valid checksum values: + * md5:090992ba9fd140077b0661cb75f7ce13 + * 090992ba9fd140077b0661cb75f7ce13 + * sha1:ebfb681885ddf1234c18094a45bbeafd91467911 + * ebfb681885ddf1234c18094a45bbeafd91467911 + * sha256:ed363350696a726b7932db864dda019bd2017365c9e299627830f06954643f93 + * ed363350696a726b7932db864dda019bd2017365c9e299627830f06954643f93 + * file:http://releases.ubuntu.com/20.04/SHA256SUMS + * file:file://./local/path/file.sum + * file:./local/path/file.sum + * none + Although the checksum will not be verified when it is set to "none", + this is not recommended since these files can be very large and + corruption does happen from time to time. -* `iso_checksum_type` (string) - The type of the checksum specified in - `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or - "sha512" currently. While "none" will skip checksumming, this is not - recommended since ISO files are generally large and corruption does happen - from time to time. * `iso_url` (string) - A URL to the ISO containing the installation image. This URL can be either an HTTP URL or a file URL (or path to a file). If this is an HTTP URL, Packer will download it and cache it between runs. -* `remote_host` (string) - The host of the Xenserver / XCP-ng pool primary. Typically these will be specified through environment variables as seen in the [examples](../../examples/centos8.json). +* `remote_host` (string) - The host of the Xenserver / XCP-ng pool primary. Typically these will be specified through environment variables as seen in the [examples](../../../examples). * `remote_username` (string) - The XenServer username used to access the remote machine. @@ -59,7 +72,7 @@ each category, the available options are alphabetized and described. be to type just enough to initialize the operating system installer. Special keys can be typed as well, and are covered in the section below on the boot command. If this is not specified, it is assumed the installer will start - itself. See the [Ubuntu](../../examples/ubuntu-2004.json) and [centos](../../examples/centos8.json) examples to see how these are used to launch autoinstall and kickstart respectively. + itself. See the [Ubuntu](../../../examples/ubuntu) and [centos](../../../examples/centos) examples to see how these are used to launch autoinstall and kickstart respectively. * `boot_wait` (string) - The time to wait after booting the initial virtual machine before typing the `boot_command`. The value of this should be @@ -198,6 +211,8 @@ each category, the available options are alphabetized and described. * `vm_memory` (integer) - The size, in megabytes, of the amount of memory to allocate for the VM. By default, this is 1024 (1 GB). +* `vm_tags` (array of strings) - A list of tags to add to the VM + ## Differences with other Packer builders Currently the XenServer builder has some quirks when compared with other Packer builders. @@ -256,4 +271,4 @@ The available variables are: configuration parameter. If `http_directory` isn't specified, these will be blank! -See the [examples](../../examples/) for working boot commands. +See the [examples](../../../examples) for working boot commands. diff --git a/examples/centos/centos8-local.pkr.hcl b/examples/centos/centos8-local.pkr.hcl index c21eb8f..5dc693e 100644 --- a/examples/centos/centos8-local.pkr.hcl +++ b/examples/centos/centos8-local.pkr.hcl @@ -47,8 +47,7 @@ locals { } source "xenserver-iso" "centos8-local" { - iso_checksum = "aaf9d4b3071c16dbbda01dfe06085e5d0fdac76df323e3bbe87cce4318052247" - iso_checksum_type = "sha1" + iso_checksum = "sha1:aaf9d4b3071c16dbbda01dfe06085e5d0fdac76df323e3bbe87cce4318052247" iso_url = "http://mirrors.ocf.berkeley.edu/centos/8.3.2011/isos/x86_64/CentOS-8.3.2011-x86_64-dvd1.iso" sr_iso_name = var.sr_iso_name diff --git a/examples/centos/centos8-netinstall.pkr.hcl b/examples/centos/centos8-netinstall.pkr.hcl index e34d369..c2abbfe 100644 --- a/examples/centos/centos8-netinstall.pkr.hcl +++ b/examples/centos/centos8-netinstall.pkr.hcl @@ -47,8 +47,7 @@ locals { } source "xenserver-iso" "centos8-netinstall" { - iso_checksum = "07a8e59c42cc086ec4c49bdce4fae5a17b077dea" - iso_checksum_type = "sha1" + iso_checksum = "sha1:07a8e59c42cc086ec4c49bdce4fae5a17b077dea" iso_url = "http://mirrors.ocf.berkeley.edu/centos/8.3.2011/isos/x86_64/CentOS-8.3.2011-x86_64-boot.iso" sr_iso_name = var.sr_iso_name diff --git a/examples/ubuntu/ubuntu-2004.pkr.hcl b/examples/ubuntu/ubuntu-2004.pkr.hcl index 9879dcd..c9ebfa8 100644 --- a/examples/ubuntu/ubuntu-2004.pkr.hcl +++ b/examples/ubuntu/ubuntu-2004.pkr.hcl @@ -1,12 +1,43 @@ packer { required_plugins { xenserver= { - version = ">= v0.3.2" + version = ">= v0.5.2" source = "github.com/ddelnano/xenserver" } } } +# The ubuntu_version value determines what Ubuntu iso URL and sha256 hash we lookup. Updating +# this will allow a new version to be pulled in. +data "null" "ubuntu_version" { + input = "20.04" +} + +locals { + timestamp = regex_replace(timestamp(), "[- TZ:]", "") + ubuntu_version = data.null.ubuntu_version.output + + # Update this map to support future releases. At this time, the Ubuntu + # jammy template is not available yet. + ubuntu_template_name = { + 20.04 = "Ubuntu Focal Fossa 20.04" + } +} + +# TODO(ddelnano): Update this to use a local once https://github.com/hashicorp/packer/issues/11011 +# is fixed. +data "http" "ubuntu_sha_and_release" { + url = "https://releases.ubuntu.com/${data.null.ubuntu_version.output}/SHA256SUMS" +} + +local "ubuntu_sha256" { + expression = regex("([A-Za-z0-9]+)[\\s\\*]+ubuntu-.*server", data.http.ubuntu_sha_and_release.body) +} + +local "ubuntu_url_path" { + expression = regex("[A-Za-z0-9]+[\\s\\*]+ubuntu-${local.ubuntu_version}.(\\d+)-live-server-amd64.iso", data.http.ubuntu_sha_and_release.body) +} + variable "remote_host" { type = string description = "The ip or fqdn of your XenServer. This will be pulled from the env var 'PKR_VAR_XAPI_HOST'" @@ -42,15 +73,9 @@ variable "sr_name" { description = "The name of the SR to packer will use" } -locals { - timestamp = regex_replace(timestamp(), "[- TZ:]", "") -} - - source "xenserver-iso" "ubuntu-2004" { - iso_checksum = "d1f2bf834bbe9bb43faf16f9be992a6f3935e65be0edece1dee2aa6eb1767423" - iso_checksum_type = "sha256" - iso_url = "http://releases.ubuntu.com/20.04/ubuntu-20.04.2-live-server-amd64.iso" + iso_checksum = "sha256:${local.ubuntu_sha256.0}" + iso_url = "https://releases.ubuntu.com/${local.ubuntu_version}/ubuntu-${local.ubuntu_version}.${local.ubuntu_url_path.0}-live-server-amd64.iso" sr_iso_name = var.sr_iso_name sr_name = var.sr_name @@ -60,10 +85,12 @@ source "xenserver-iso" "ubuntu-2004" { remote_password = var.remote_password remote_username = var.remote_username - vm_name = "packer-ubuntu-2004-${local.timestamp}" + # Change this to match the ISO of ubuntu you are using in the iso_url variable + clone_template = local.ubuntu_template_name[data.null.ubuntu_version.output] + vm_name = "packer-ubuntu-${data.null.ubuntu_version.output}-${local.timestamp}" vm_description = "Build started: ${local.timestamp}" vm_memory = 4096 - disk_size = 20000 + disk_size = 30720 floppy_files = [ "examples/http/ubuntu-2004/meta-data",