Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add boot steps configuration option #103

Merged
merged 8 commits into from Nov 9, 2022
45 changes: 45 additions & 0 deletions builder/qemu/config.go
Expand Up @@ -520,6 +520,46 @@ type Config struct {
// * ARM: tpm-tis-device
// * PPC (p-series): tpm-spapr
TPMType string `mapstructure:"tpm_device_type" required:"false"`
// This is an array of tuples of boot commands, to type when the virtual
// machine is booted. The first element of the tuple is the actual boot
// command. The second element of the tuple, which is optional, is a
// description of what the boot command does. This is intended to be used for
// interactive installers that requires many commands to complete the
// installation. Both the command and the description will be printed when
// logging is enabled. When debug mode is enabled Packer will pause after
// typing each boot command. This will make it easier to follow along the
// installation process and make sure the Packer and the installer are in
// sync. `boot_steps` and `boot_commands` are mutually exclusive.
//
// Example:
//
// In HCL:
// ```hcl
// boot_steps = [
// ["1<enter><wait5>", "Install NetBSD"],
// ["a<enter><wait5>", "Installation messages in English"],
// ["a<enter><wait5>", "Keyboard type: unchanged"],
//
// ["a<enter><wait5>", "Install NetBSD to hard disk"],
// ["b<enter><wait5>", "Yes"]
// ]
// ```
//
// In JSON:
// ```json
// {
// "boot_steps": [
// ["1<enter><wait5>", "Install NetBSD"],
// ["a<enter><wait5>", "Installation messages in English"],
// ["a<enter><wait5>", "Keyboard type: unchanged"],
//
// ["a<enter><wait5>", "Install NetBSD to hard disk"],
// ["b<enter><wait5>", "Yes"]
// ]
// }
jacob-carlborg marked this conversation as resolved.
Show resolved Hide resolved
// ```
BootSteps [][]string `mapstructure:"boot_steps" required:"false"`

// TODO(mitchellh): deprecate
RunOnce bool `mapstructure:"run_once"`

Expand Down Expand Up @@ -758,6 +798,11 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs, fmt.Errorf("net_bridge is only supported in Linux based OSes"))
}

if len(c.BootCommand) > 0 && len(c.BootSteps) > 0 {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("boot_command and boot_steps cannot be used together"))
}

if c.NetBridge != "" || c.VNCUsePassword {
c.QMPEnable = true
}
Expand Down
2 changes: 2 additions & 0 deletions builder/qemu/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 65 additions & 25 deletions builder/qemu/step_type_boot_command.go
Expand Up @@ -35,6 +35,20 @@ type bootCommandTemplateData struct {
type stepTypeBootCommand struct{}

func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
command := config.VNCConfig.FlatBootCommand()
bootSteps := config.BootSteps

if len(command) > 0 {
bootSteps = [][]string{{command}}
}

return typeBootCommands(ctx, state, bootSteps)
}

func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}

func typeBootCommands(ctx context.Context, state multistep.StateBag, bootSteps [][]string) multistep.StepAction {
config := state.Get("config").(*Config)
debug := state.Get("debug").(bool)
httpPort := state.Get("http_port").(int)
Expand Down Expand Up @@ -105,35 +119,61 @@ func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)

d := bootcommand.NewVNCDriver(c, config.VNCConfig.BootKeyInterval)

ui.Say("Typing the boot command over VNC...")
command, err := interpolate.Render(config.VNCConfig.FlatBootCommand(), &configCtx)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Typing the boot commands over VNC...")

seq, err := bootcommand.GenerateExpressionSequence(command)
if err != nil {
err := fmt.Errorf("Error generating boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for _, step := range bootSteps {
if len(step) == 0 {
continue
}

if err := seq.Do(ctx, d); err != nil {
err := fmt.Errorf("Error running boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var description string

if len(step) >= 2 {
description = step[1]
} else {
description = ""
}

if len(description) > 0 {
ui.Say(fmt.Sprintf("Typing boot command for: %s", description))
}

if pauseFn != nil {
pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command: %s", command), state)
command, err := interpolate.Render(step[0], &configCtx)

if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

seq, err := bootcommand.GenerateExpressionSequence(command)
if err != nil {
err := fmt.Errorf("Error generating boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

if err := seq.Do(ctx, d); err != nil {
err := fmt.Errorf("Error running boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

if pauseFn != nil {
var message string

if len(description) > 0 {
message = fmt.Sprintf("boot description: \"%s\", command: %s", description, command)
} else {
message = fmt.Sprintf("boot_command: %s", command)
}

pauseFn(multistep.DebugLocationAfterRun, message, state)
}
}

return multistep.ActionContinue
}

func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
39 changes: 39 additions & 0 deletions docs-partials/builder/qemu/Config-not-required.mdx
Expand Up @@ -350,4 +350,43 @@
* ARM: tpm-tis-device
* PPC (p-series): tpm-spapr

- `boot_steps` ([][]string) - This is an array of tuples of boot commands, to type when the virtual
machine is booted. The first element of the tuple is the actual boot
command. The second element of the tuple, which is optional, is a
description of what the boot command does. This is intended to be used for
interactive installers that requires many commands to complete the
installation. Both the command and the description will be printed when
logging is enabled. When debug mode is enabled Packer will pause after
typing each boot command. This will make it easier to follow along the
installation process and make sure the Packer and the installer are in
sync. `boot_steps` and `boot_commands` are mutually exclusive.

Example:

In HCL:
```hcl
boot_steps = [
["1<enter><wait5>", "Install NetBSD"],
["a<enter><wait5>", "Installation messages in English"],
["a<enter><wait5>", "Keyboard type: unchanged"],

["a<enter><wait5>", "Install NetBSD to hard disk"],
["b<enter><wait5>", "Yes"]
]
```

In JSON:
```json
{
"boot_steps": [
["1<enter><wait5>", "Install NetBSD"],
["a<enter><wait5>", "Installation messages in English"],
["a<enter><wait5>", "Keyboard type: unchanged"],

["a<enter><wait5>", "Install NetBSD to hard disk"],
["b<enter><wait5>", "Yes"]
]
}
```

<!-- End of code generated from the comments of the Config struct in builder/qemu/config.go; -->