diff --git a/.web-docs/components/builder/chroot/README.md b/.web-docs/components/builder/chroot/README.md index 91e0e059..a91bba17 100644 --- a/.web-docs/components/builder/chroot/README.md +++ b/.web-docs/components/builder/chroot/README.md @@ -122,6 +122,10 @@ necessary for this build to succeed and can be found further down the page. template where the .Device variable is replaced with the name of the device where the volume is attached. +- `manual_mount_command` (string) - Manual Mount Command that is executed to manually mount the + root device, partition, and unmount. All other mount steps are skipped. + The device andmount path are provided by `{{.Device}}` and `{{.MountPath}}`. + - `post_mount_commands` ([]string) - As pre_mount_commands, but the commands are executed after mounting the root device and before the extra mount and copy steps. The device and mount path are provided by `{{.Device}}` and `{{.MountPath}}`. diff --git a/builder/chroot/builder.go b/builder/chroot/builder.go index ef40108d..4a2f6ed9 100644 --- a/builder/chroot/builder.go +++ b/builder/chroot/builder.go @@ -98,6 +98,10 @@ type Config struct { // template where the .Device variable is replaced with the name of the // device where the volume is attached. MountPath string `mapstructure:"mount_path" required:"false"` + // Manual Mount Command that is executed to manually mount the + // root device, partition, and unmount. All other mount steps are skipped. + // The device andmount path are provided by `{{.Device}}` and `{{.MountPath}}`. + ManualMountCommand string `mapstructure:"manual_mount_command" required:"false"` // As pre_mount_commands, but the commands are executed after mounting the // root device and before the extra mount and copy steps. The device and // mount path are provided by `{{.Device}}` and `{{.MountPath}}`. @@ -245,6 +249,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { "root_volume_tag", "command_wrapper", "post_mount_commands", + "manual_mount_command", "pre_mount_commands", "mount_path", }, @@ -321,9 +326,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { errs = packersdk.MultiErrorAppend( errs, errors.New("root_volume_size is required with from_scratch.")) } - if len(b.config.PreMountCommands) == 0 { + if b.config.ManualMountCommand == "" && len(b.config.PreMountCommands) == 0 { errs = packersdk.MultiErrorAppend( - errs, errors.New("pre_mount_commands is required with from_scratch.")) + errs, errors.New("pre_mount_commands or manual_mount_command is required with from_scratch.")) } if b.config.AMIVirtType == "" { errs = packersdk.MultiErrorAppend( @@ -480,17 +485,32 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) PollingConfig: b.config.PollingConfig, }, &StepEarlyUnflock{}, - &chroot.StepPreMountCommands{ - Commands: b.config.PreMountCommands, - }, - &StepMountDevice{ - MountOptions: b.config.MountOptions, - MountPartition: b.config.MountPartition, - GeneratedData: generatedData, - }, - &chroot.StepPostMountCommands{ - Commands: b.config.PostMountCommands, - }, + ) + + if b.config.ManualMountCommand != "" { + steps = append(steps, + &StepManualMountCommand{ + Command: b.config.ManualMountCommand, + GeneratedData: generatedData, + }, + ) + } else { + steps = append(steps, + &chroot.StepPreMountCommands{ + Commands: b.config.PreMountCommands, + }, + &StepMountDevice{ + MountOptions: b.config.MountOptions, + MountPartition: b.config.MountPartition, + GeneratedData: generatedData, + }, + &chroot.StepPostMountCommands{ + Commands: b.config.PostMountCommands, + }, + ) + } + + steps = append(steps, &chroot.StepMountExtra{ ChrootMounts: b.config.ChrootMounts, }, diff --git a/builder/chroot/builder.hcl2spec.go b/builder/chroot/builder.hcl2spec.go index 19f505bc..8b53e006 100644 --- a/builder/chroot/builder.hcl2spec.go +++ b/builder/chroot/builder.hcl2spec.go @@ -74,6 +74,7 @@ type FlatConfig struct { MountOptions []string `mapstructure:"mount_options" required:"false" cty:"mount_options" hcl:"mount_options"` MountPartition *string `mapstructure:"mount_partition" required:"false" cty:"mount_partition" hcl:"mount_partition"` MountPath *string `mapstructure:"mount_path" required:"false" cty:"mount_path" hcl:"mount_path"` + ManualMountCommand *string `mapstructure:"manual_mount_command" required:"false" cty:"manual_mount_command" hcl:"manual_mount_command"` PostMountCommands []string `mapstructure:"post_mount_commands" required:"false" cty:"post_mount_commands" hcl:"post_mount_commands"` PreMountCommands []string `mapstructure:"pre_mount_commands" required:"false" cty:"pre_mount_commands" hcl:"pre_mount_commands"` RootDeviceName *string `mapstructure:"root_device_name" required:"false" cty:"root_device_name" hcl:"root_device_name"` @@ -165,6 +166,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "mount_options": &hcldec.AttrSpec{Name: "mount_options", Type: cty.List(cty.String), Required: false}, "mount_partition": &hcldec.AttrSpec{Name: "mount_partition", Type: cty.String, Required: false}, "mount_path": &hcldec.AttrSpec{Name: "mount_path", Type: cty.String, Required: false}, + "manual_mount_command": &hcldec.AttrSpec{Name: "manual_mount_command", Type: cty.String, Required: false}, "post_mount_commands": &hcldec.AttrSpec{Name: "post_mount_commands", Type: cty.List(cty.String), Required: false}, "pre_mount_commands": &hcldec.AttrSpec{Name: "pre_mount_commands", Type: cty.List(cty.String), Required: false}, "root_device_name": &hcldec.AttrSpec{Name: "root_device_name", Type: cty.String, Required: false}, diff --git a/builder/chroot/step_manual_mount_command.go b/builder/chroot/step_manual_mount_command.go new file mode 100644 index 00000000..56c02b55 --- /dev/null +++ b/builder/chroot/step_manual_mount_command.go @@ -0,0 +1,117 @@ +package chroot + +import ( + "bytes" + "context" + "fmt" + "log" + "path/filepath" + + "github.com/hashicorp/packer-plugin-sdk/common" + "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/packerbuilderdata" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" +) + +type manualMountCommandData struct { + Device string +} + +// StepManualMountCommand sets up the a new block device when building from scratch +type StepManualMountCommand struct { + Command string + mountPath string + + GeneratedData *packerbuilderdata.GeneratedData +} + +func (s *StepManualMountCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + device := state.Get("device").(string) + ui := state.Get("ui").(packersdk.Ui) + + ui.Say("Running manual mount commands...") + + if config.NVMEDevicePath != "" { + // customizable device path for mounting NVME block devices on c5 and m5 HVM + device = config.NVMEDevicePath + } + ui.Say(fmt.Sprintf("Command is: %s", s.Command)) + if len(s.Command) == 0 { + return multistep.ActionContinue + } + + ictx := config.GetContext() + ictx.Data = &manualMountCommandData{Device: filepath.Base(device)} + mountPath, err := interpolate.Render(config.MountPath, &ictx) + + if err != nil { + err := fmt.Errorf("Error preparing mount directory: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + mountPath, err = filepath.Abs(mountPath) + if err != nil { + err := fmt.Errorf("Error preparing mount directory: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Mount Path After ABS is: %s", mountPath)) + + log.Printf("Mount path: %s", mountPath) + stderr := new(bytes.Buffer) + + wrappedCommand := state.Get("wrappedCommand").(common.CommandWrapper) + + ui.Say("Running manual mount commands...") + mountCommand, err := wrappedCommand(s.Command) + if err != nil { + err := fmt.Errorf("Error creating mount command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + cmd := common.ShellCommand(mountCommand) + cmd.Stderr = stderr + if err := cmd.Run(); err != nil { + err := fmt.Errorf( + "Error mounting root volume: %s\nStderr: %s", err, stderr.String()) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Set the mount path so we remember to unmount it later + s.mountPath = mountPath + state.Put("mount_path", s.mountPath) + s.GeneratedData.Put("MountPath", s.mountPath) + state.Put("mount_device_cleanup", s) + + return multistep.ActionContinue +} + +func (s *StepManualMountCommand) Cleanup(state multistep.StateBag) { + ui := state.Get("ui").(packersdk.Ui) + if err := s.CleanupFunc(state); err != nil { + ui.Error(err.Error()) + } +} + +func (s *StepManualMountCommand) CleanupFunc(state multistep.StateBag) error { + if s.mountPath == "" { + return nil + } + + ui := state.Get("ui").(packersdk.Ui) + + ui.Say("Skipping UnMount Root Mount, it must be manually unmounted...") + + s.mountPath = "" + return nil +} diff --git a/docs-partials/builder/chroot/Config-not-required.mdx b/docs-partials/builder/chroot/Config-not-required.mdx index 1e2d9b55..8d18a177 100644 --- a/docs-partials/builder/chroot/Config-not-required.mdx +++ b/docs-partials/builder/chroot/Config-not-required.mdx @@ -60,6 +60,10 @@ template where the .Device variable is replaced with the name of the device where the volume is attached. +- `manual_mount_command` (string) - Manual Mount Command that is executed to manually mount the + root device, partition, and unmount. All other mount steps are skipped. + The device andmount path are provided by `{{.Device}}` and `{{.MountPath}}`. + - `post_mount_commands` ([]string) - As pre_mount_commands, but the commands are executed after mounting the root device and before the extra mount and copy steps. The device and mount path are provided by `{{.Device}}` and `{{.MountPath}}`.