diff --git a/.web-docs/components/builder/clone/README.md b/.web-docs/components/builder/clone/README.md index 46e0ae19..54bd5f04 100644 --- a/.web-docs/components/builder/clone/README.md +++ b/.web-docs/components/builder/clone/README.md @@ -276,6 +276,8 @@ boot time. For example `-no-reboot -smbios type=0,vendor=FOO`. Note: this option is for experts only. +- `before_start_hook` ([]string) - Before Start Hook + diff --git a/.web-docs/components/builder/iso/README.md b/.web-docs/components/builder/iso/README.md index 53305097..29fd242e 100644 --- a/.web-docs/components/builder/iso/README.md +++ b/.web-docs/components/builder/iso/README.md @@ -207,6 +207,8 @@ in the image's Cloud-Init settings for provisioning. For example `-no-reboot -smbios type=0,vendor=FOO`. Note: this option is for experts only. +- `before_start_hook` ([]string) - Before Start Hook + diff --git a/builder/proxmox/clone/config.hcl2spec.go b/builder/proxmox/clone/config.hcl2spec.go index aa53e202..9edde42e 100644 --- a/builder/proxmox/clone/config.hcl2spec.go +++ b/builder/proxmox/clone/config.hcl2spec.go @@ -118,6 +118,7 @@ type FlatConfig struct { AdditionalISOFiles []proxmox.FlatadditionalISOsConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"` VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"` AdditionalArgs *string `mapstructure:"qemu_additional_args" cty:"qemu_additional_args" hcl:"qemu_additional_args"` + BeforeStartHook []string `mapstructure:"before_start_hook" cty:"before_start_hook" hcl:"before_start_hook"` CloneVM *string `mapstructure:"clone_vm" required:"true" cty:"clone_vm" hcl:"clone_vm"` CloneVMID *int `mapstructure:"clone_vm_id" required:"true" cty:"clone_vm_id" hcl:"clone_vm_id"` FullClone *bool `mapstructure:"full_clone" required:"false" cty:"full_clone" hcl:"full_clone"` @@ -245,6 +246,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*proxmox.FlatadditionalISOsConfig)(nil).HCL2Spec())}, "vm_interface": &hcldec.AttrSpec{Name: "vm_interface", Type: cty.String, Required: false}, "qemu_additional_args": &hcldec.AttrSpec{Name: "qemu_additional_args", Type: cty.String, Required: false}, + "before_start_hook": &hcldec.AttrSpec{Name: "before_start_hook", Type: cty.List(cty.String), Required: false}, "clone_vm": &hcldec.AttrSpec{Name: "clone_vm", Type: cty.String, Required: false}, "clone_vm_id": &hcldec.AttrSpec{Name: "clone_vm_id", Type: cty.Number, Required: false}, "full_clone": &hcldec.AttrSpec{Name: "full_clone", Type: cty.Bool, Required: false}, diff --git a/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index f6416ce5..3a365336 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -196,6 +196,8 @@ type Config struct { // Note: this option is for experts only. AdditionalArgs string `mapstructure:"qemu_additional_args"` + BeforeStartHook []string `mapstructure:"before_start_hook"` + Ctx interpolate.Context `mapstructure-to-hcl2:",skip"` } diff --git a/builder/proxmox/common/config.hcl2spec.go b/builder/proxmox/common/config.hcl2spec.go index e5cf36f9..12187805 100644 --- a/builder/proxmox/common/config.hcl2spec.go +++ b/builder/proxmox/common/config.hcl2spec.go @@ -117,6 +117,7 @@ type FlatConfig struct { AdditionalISOFiles []FlatadditionalISOsConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"` VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"` AdditionalArgs *string `mapstructure:"qemu_additional_args" cty:"qemu_additional_args" hcl:"qemu_additional_args"` + BeforeStartHook []string `mapstructure:"before_start_hook" cty:"before_start_hook" hcl:"before_start_hook"` } // FlatMapstructure returns a new FlatConfig. @@ -238,6 +239,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*FlatadditionalISOsConfig)(nil).HCL2Spec())}, "vm_interface": &hcldec.AttrSpec{Name: "vm_interface", Type: cty.String, Required: false}, "qemu_additional_args": &hcldec.AttrSpec{Name: "qemu_additional_args", Type: cty.String, Required: false}, + "before_start_hook": &hcldec.AttrSpec{Name: "before_start_hook", Type: cty.List(cty.String), Required: false}, } return s } diff --git a/builder/proxmox/common/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index 4c9bd859..e4843796 100644 --- a/builder/proxmox/common/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -4,9 +4,12 @@ package proxmox import ( + "bytes" "context" + "encoding/json" "fmt" "log" + "os/exec" "strconv" "strings" @@ -243,6 +246,69 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist // info available in the vmref type. state.Put("instance_id", vmRef.VmId()) + // Execute the before_start_hook. + if len(c.BeforeStartHook) > 0 { + ui.Say("Executing the before_start_hook") + + cfg, err := client.GetVmConfig(vmRef) + if err != nil { + err := fmt.Errorf("failed to get the VM configuration: %w", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + cfgDoc, err := json.Marshal(cfg) + if err != nil { + err := fmt.Errorf("failed to marshal the VM configuration: %w", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Printf("executing the before_start_hook in VM with ID %d and config %s", vmRef.VmId(), cfgDoc) + + var stdout, stderr bytes.Buffer + var cmdArgs []string + if len(c.BeforeStartHook) > 1 { + cmdArgs = c.BeforeStartHook[1:] + } + cmd := exec.Command(c.BeforeStartHook[0], cmdArgs...) + cmd.Stdin = bytes.NewReader(cfgDoc) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err = cmd.Run() + if err != nil { + log.Printf("failed to execute the before_start_hook in VM with ID %d. stderr=%s stdout=%s", vmRef.VmId(), stderr.Bytes(), stdout.Bytes()) + err := fmt.Errorf("failed to execute the before_start_hook: %w", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + newCfgDoc := stdout.Bytes() + + log.Printf("executed the before_start_hook in VM with ID %d and returned the config %s", vmRef.VmId(), newCfgDoc) + + if bytes.HasPrefix(newCfgDoc, []byte{'{'}) { + var vmConfig map[string]interface{} + err = json.Unmarshal(newCfgDoc, &vmConfig) + if err != nil { + err := fmt.Errorf("failed to unmarshal the before_start_hook output: %w", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + _, err = client.SetVmConfig(vmRef, vmConfig) + if err != nil { + err := fmt.Errorf("failed to set the VM configuration: %w", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + } + ui.Say("Starting VM") _, err := client.StartVm(vmRef) if err != nil { diff --git a/builder/proxmox/iso/config.hcl2spec.go b/builder/proxmox/iso/config.hcl2spec.go index 7a275567..ca02b3c7 100644 --- a/builder/proxmox/iso/config.hcl2spec.go +++ b/builder/proxmox/iso/config.hcl2spec.go @@ -118,6 +118,7 @@ type FlatConfig struct { AdditionalISOFiles []proxmox.FlatadditionalISOsConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"` VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"` AdditionalArgs *string `mapstructure:"qemu_additional_args" cty:"qemu_additional_args" hcl:"qemu_additional_args"` + BeforeStartHook []string `mapstructure:"before_start_hook" cty:"before_start_hook" hcl:"before_start_hook"` ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"` RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"` ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"` @@ -248,6 +249,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*proxmox.FlatadditionalISOsConfig)(nil).HCL2Spec())}, "vm_interface": &hcldec.AttrSpec{Name: "vm_interface", Type: cty.String, Required: false}, "qemu_additional_args": &hcldec.AttrSpec{Name: "qemu_additional_args", Type: cty.String, Required: false}, + "before_start_hook": &hcldec.AttrSpec{Name: "before_start_hook", Type: cty.List(cty.String), Required: false}, "iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false}, "iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false}, "iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false}, diff --git a/docs-partials/builder/proxmox/common/Config-not-required.mdx b/docs-partials/builder/proxmox/common/Config-not-required.mdx index 643bb2ff..710620ea 100644 --- a/docs-partials/builder/proxmox/common/Config-not-required.mdx +++ b/docs-partials/builder/proxmox/common/Config-not-required.mdx @@ -139,4 +139,6 @@ For example `-no-reboot -smbios type=0,vendor=FOO`. Note: this option is for experts only. +- `before_start_hook` ([]string) - Before Start Hook + diff --git a/docs-partials/builder/proxmox/common/hooks-not-required.mdx b/docs-partials/builder/proxmox/common/hooks-not-required.mdx new file mode 100644 index 00000000..3fc96384 --- /dev/null +++ b/docs-partials/builder/proxmox/common/hooks-not-required.mdx @@ -0,0 +1,5 @@ + + +- `before_start` ([]string) - Before Start + +