Add chroot disk build

This commit is contained in:
Miłosz Smółka 2019-01-28 12:30:15 +01:00
parent aaee600be0
commit bd4ce90728
16 changed files with 720 additions and 20 deletions

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/hyperonecom/h1-client-go" "github.com/hyperonecom/h1-client-go"
) )
@ -41,12 +42,23 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
return nil, nil return nil, nil
} }
type wrappedCommandTemplate struct {
Command string
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
wrappedCommand := func(command string) (string, error) {
ctx := b.config.ctx
ctx.Data = &wrappedCommandTemplate{Command: command}
return interpolate.Render(b.config.ChrootCommandWrapper, &ctx)
}
state := &multistep.BasicStateBag{} state := &multistep.BasicStateBag{}
state.Put("config", &b.config) state.Put("config", &b.config)
state.Put("client", b.client) state.Put("client", b.client)
state.Put("hook", hook) state.Put("hook", hook)
state.Put("ui", ui) state.Put("ui", ui)
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
steps := []multistep.Step{ steps := []multistep.Step{
&stepCreateSSHKey{}, &stepCreateSSHKey{},
@ -56,9 +68,28 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Host: getPublicIP, Host: getPublicIP,
SSHConfig: b.config.Comm.SSHConfigFunc(), SSHConfig: b.config.Comm.SSHConfigFunc(),
}, },
&common.StepProvision{}, }
&stepStopVM{},
&stepCreateImage{}, if b.config.ChrootDisk {
steps = append(steps,
&stepPrepareDevice{},
&stepPreMountCommands{},
&stepMountChroot{},
&stepPostMountCommands{},
&stepMountExtra{},
&stepCopyFiles{},
&stepChrootProvision{},
&stepStopVM{},
&stepDetachDisk{},
&stepCreateVMFromDisk{},
&stepCreateImage{},
)
} else {
steps = append(steps,
&common.StepProvision{},
&stepStopVM{},
&stepCreateImage{},
)
} }
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)

View File

@ -0,0 +1,54 @@
package hyperone
import (
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"github.com/hashicorp/packer/packer"
)
type CommandWrapper func(string) (string, error)
// ChrootCommunicator works as a wrapper on SSHCommunicator, modyfing paths in
// flight to be run in a chroot.
type ChrootCommunicator struct {
Chroot string
CmdWrapper CommandWrapper
Wrapped packer.Communicator
}
func (c *ChrootCommunicator) Start(cmd *packer.RemoteCmd) error {
command := strconv.Quote(cmd.Command)
chrootCommand, err := c.CmdWrapper(
fmt.Sprintf("sudo chroot %s /bin/sh -c %s", c.Chroot, command))
if err != nil {
return err
}
cmd.Command = chrootCommand
return c.Wrapped.Start(cmd)
}
func (c *ChrootCommunicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
dst = filepath.Join(c.Chroot, dst)
return c.Wrapped.Upload(dst, r, fi)
}
func (c *ChrootCommunicator) UploadDir(dst string, src string, exclude []string) error {
dst = filepath.Join(c.Chroot, dst)
return c.Wrapped.UploadDir(dst, src, exclude)
}
func (c *ChrootCommunicator) Download(src string, w io.Writer) error {
src = filepath.Join(c.Chroot, src)
return c.Wrapped.Download(src, w)
}
func (c *ChrootCommunicator) DownloadDir(src string, dst string, exclude []string) error {
src = filepath.Join(c.Chroot, src)
return c.Wrapped.DownloadDir(src, dst, exclude)
}

View File

@ -57,6 +57,19 @@ type Config struct {
PrivateIP string `mapstructure:"private_ip"` PrivateIP string `mapstructure:"private_ip"`
PublicIP string `mapstructure:"public_ip"` PublicIP string `mapstructure:"public_ip"`
ChrootDisk bool `mapstructure:"chroot_disk"`
ChrootDiskSize float32 `mapstructure:"chroot_disk_size"`
ChrootDiskType string `mapstructure:"chroot_disk_type"`
ChrootMountPath string `mapstructure:"chroot_mount_path"`
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
ChrootCopyFiles []string `mapstructure:"chroot_copy_files"`
ChrootCommandWrapper string `mapstructure:"chroot_command_wrapper"`
MountOptions []string `mapstructure:"mount_options"`
MountPartition string `mapstructure:"mount_partition"`
PreMountCommands []string `mapstructure:"pre_mount_commands"`
PostMountCommands []string `mapstructure:"post_mount_commands"`
SSHKeys []string `mapstructure:"ssh_keys"` SSHKeys []string `mapstructure:"ssh_keys"`
UserData string `mapstructure:"user_data"` UserData string `mapstructure:"user_data"`
@ -74,6 +87,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
InterpolateFilter: &interpolate.RenderFilter{ InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{ Exclude: []string{
"run_command", "run_command",
"chroot_command_wrapper",
"post_mount_commands",
"pre_mount_commands",
"mount_path",
}, },
}, },
}, raws...) }, raws...)
@ -138,6 +155,45 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
c.DiskType = defaultDiskType c.DiskType = defaultDiskType
} }
if c.ChrootCommandWrapper == "" {
c.ChrootCommandWrapper = "{{.Command}}"
}
if c.ChrootDiskSize == 0 {
c.ChrootDiskSize = c.DiskSize
}
if c.ChrootDiskType == "" {
c.ChrootDiskType = c.DiskType
}
if c.ChrootMountPath == "" {
c.ChrootMountPath = "/mnt/packer-hyperone-volumes/{{timestamp}}"
}
if c.ChrootMounts == nil {
c.ChrootMounts = make([][]string, 0)
}
if len(c.ChrootMounts) == 0 {
c.ChrootMounts = [][]string{
{"proc", "proc", "/proc"},
{"sysfs", "sysfs", "/sys"},
{"bind", "/dev", "/dev"},
{"devpts", "devpts", "/dev/pts"},
{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
}
}
if c.ChrootCopyFiles == nil {
c.ChrootCopyFiles = []string{"/etc/resolv.conf"}
}
if c.MountPartition == "" {
c.MountPartition = "1"
}
// Validation
var errs *packer.MultiError var errs *packer.MultiError
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 { if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...) errs = packer.MultiErrorAppend(errs, es...)
@ -159,6 +215,20 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
errs = packer.MultiErrorAppend(errs, errors.New("source image is required")) errs = packer.MultiErrorAppend(errs, errors.New("source image is required"))
} }
if c.ChrootDisk {
if len(c.PreMountCommands) == 0 {
errs = packer.MultiErrorAppend(errs, errors.New("pre-mount commands are required for chroot disk"))
}
}
for _, mounts := range c.ChrootMounts {
if len(mounts) != 3 {
errs = packer.MultiErrorAppend(
errs, errors.New("each chroot_mounts entry should have three elements"))
break
}
}
if errs != nil && len(errs.Errors) > 0 { if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs return nil, nil, errs
} }

View File

@ -0,0 +1,28 @@
package hyperone
import (
"context"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepChrootProvision struct{}
func (s *stepChrootProvision) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
sshCommunicator := state.Get("communicator").(packer.Communicator)
comm := &ChrootCommunicator{
Chroot: config.ChrootMountPath,
CmdWrapper: wrappedCommand,
Wrapped: sshCommunicator,
}
stepProvision := common.StepProvision{Comm: comm}
return stepProvision.Run(ctx, state)
}
func (s *stepChrootProvision) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,40 @@
package hyperone
import (
"context"
"fmt"
"log"
"path/filepath"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepCopyFiles struct{}
func (s *stepCopyFiles) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
if len(config.ChrootCopyFiles) == 0 {
return multistep.ActionContinue
}
ui.Say("Copying files from host to chroot...")
for _, path := range config.ChrootCopyFiles {
chrootPath := filepath.Join(config.ChrootMountPath, path)
log.Printf("Copying '%s' to '%s'", path, chrootPath)
command := fmt.Sprintf("cp --remove-destination %s %s", path, chrootPath)
err := runCommands([]string{command}, config.ctx, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *stepCopyFiles) Cleanup(state multistep.StateBag) {}

View File

@ -13,6 +13,10 @@ type stepCreateVM struct {
vmID string vmID string
} }
const (
chrootDiskName = "packer-chroot-disk"
)
func (s *stepCreateVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { func (s *stepCreateVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*openapi.APIClient) client := state.Get("client").(*openapi.APIClient)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
@ -26,17 +30,27 @@ func (s *stepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis
var sshKeys = []string{sshKey} var sshKeys = []string{sshKey}
sshKeys = append(sshKeys, config.SSHKeys...) sshKeys = append(sshKeys, config.SSHKeys...)
options := openapi.VmCreate{ disks := []openapi.VmCreateDisk{
Name: config.VmName, {
Image: config.SourceImage, Service: config.DiskType,
Service: config.VmFlavour, Size: config.DiskSize,
SshKeys: sshKeys,
Disk: []openapi.VmCreateDisk{
{
Service: config.DiskType,
Size: config.DiskSize,
},
}, },
}
if config.ChrootDisk {
disks = append(disks, openapi.VmCreateDisk{
Service: config.ChrootDiskType,
Size: config.ChrootDiskSize,
Name: chrootDiskName,
})
}
options := openapi.VmCreate{
Name: config.VmName,
Image: config.SourceImage,
Service: config.VmFlavour,
SshKeys: sshKeys,
Disk: disks,
Netadp: []openapi.VmCreateNetadp{netAdapter}, Netadp: []openapi.VmCreateNetadp{netAdapter},
UserMetadata: config.UserData, UserMetadata: config.UserData,
Tag: config.VmTags, Tag: config.VmTags,
@ -63,7 +77,11 @@ func (s *stepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis
var diskIDs []string var diskIDs []string
for _, hdd := range hdds { for _, hdd := range hdds {
diskIDs = append(diskIDs, hdd.Disk.Id) if hdd.Disk.Name == chrootDiskName {
state.Put("chroot_disk_id", hdd.Disk.Id)
} else {
diskIDs = append(diskIDs, hdd.Disk.Id)
}
} }
state.Put("disk_ids", diskIDs) state.Put("disk_ids", diskIDs)

View File

@ -0,0 +1,70 @@
package hyperone
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hyperonecom/h1-client-go"
)
type stepCreateVMFromDisk struct {
vmID string
}
func (s *stepCreateVMFromDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*openapi.APIClient)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
sshKey := state.Get("ssh_public_key").(string)
chrootDiskID := state.Get("chroot_disk_id").(string)
ui.Say("Creating VM from disk...")
options := openapi.VmCreate{
Name: config.VmName,
Service: config.VmFlavour,
Disk: []openapi.VmCreateDisk{
{
Id: chrootDiskID,
},
},
SshKeys: []string{sshKey},
Boot: false,
}
vm, _, err := client.VmApi.VmCreate(ctx, options)
if err != nil {
err := fmt.Errorf("error creating VM from disk: %s", formatOpenAPIError(err))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.vmID = vm.Id
state.Put("vm_id", vm.Id)
return multistep.ActionContinue
}
func (s *stepCreateVMFromDisk) Cleanup(state multistep.StateBag) {
if s.vmID == "" {
return
}
client := state.Get("client").(*openapi.APIClient)
ui := state.Get("ui").(packer.Ui)
chrootDiskID := state.Get("chroot_disk_id").(string)
ui.Say("Deleting VM (from disk)...")
deleteOptions := openapi.VmDelete{
RemoveDisks: []string{chrootDiskID},
}
_, err := client.VmApi.VmDelete(context.TODO(), s.vmID, deleteOptions)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting server '%s' - please delete it manually: %s", s.vmID, formatOpenAPIError(err)))
}
}

View File

@ -0,0 +1,34 @@
package hyperone
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hyperonecom/h1-client-go"
)
type stepDetachDisk struct {
vmID string
}
func (s *stepDetachDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*openapi.APIClient)
ui := state.Get("ui").(packer.Ui)
vmID := state.Get("vm_id").(string)
chrootDiskID := state.Get("chroot_disk_id").(string)
ui.Say("Detaching chroot disk...")
_, _, err := client.VmApi.VmDeleteHddDiskId(ctx, vmID, chrootDiskID)
if err != nil {
err := fmt.Errorf("error detaching disk: %s", formatOpenAPIError(err))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepDetachDisk) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,51 @@
package hyperone
import (
"context"
"fmt"
"log"
"strings"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepMountChroot struct{}
func (s *stepMountChroot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
log.Printf("Mount path: %s", config.ChrootMountPath)
ui.Say(fmt.Sprintf("Creating mount directory: %s", config.ChrootMountPath))
opts := ""
if len(config.MountOptions) > 0 {
opts = "-o " + strings.Join(config.MountOptions, " -o ")
}
deviceMount := device
if config.MountPartition != "" {
deviceMount = fmt.Sprintf("%s%s", device, config.MountPartition)
}
commands := []string{
fmt.Sprintf("mkdir -m 755 -p %s", config.ChrootMountPath),
fmt.Sprintf("mount %s %s %s", opts, deviceMount, config.ChrootMountPath),
}
err := runCommands(commands, config.ctx, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("mount_path", config.ChrootMountPath)
return multistep.ActionContinue
}
func (s *stepMountChroot) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,45 @@
package hyperone
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepMountExtra struct{}
func (s *stepMountExtra) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Mounting additional paths within the chroot...")
for _, mountInfo := range config.ChrootMounts {
innerPath := mountPath + mountInfo[2]
flags := "-t " + mountInfo[0]
if mountInfo[0] == "bind" {
flags = "--bind"
}
ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2]))
commands := []string{
fmt.Sprintf("mkdir -m 755 -p %s", innerPath),
fmt.Sprintf("mount %s %s %s", flags, mountInfo[1], innerPath),
}
err := runCommands(commands, config.ctx, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *stepMountExtra) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,37 @@
package hyperone
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type postMountCommandsData struct {
Device string
MountPath string
}
type stepPostMountCommands struct{}
func (s *stepPostMountCommands) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
ctx := config.ctx
ctx.Data = &postMountCommandsData{
Device: device,
MountPath: config.ChrootMountPath,
}
ui.Say("Running post-mount commands...")
if err := runCommands(config.PostMountCommands, ctx, state); err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepPostMountCommands) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,37 @@
package hyperone
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type preMountCommandsData struct {
Device string
MountPath string
}
type stepPreMountCommands struct{}
func (s *stepPreMountCommands) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
ctx := config.ctx
ctx.Data = &preMountCommandsData{
Device: device,
MountPath: config.ChrootMountPath,
}
ui.Say("Running pre-mount commands...")
if err := runCommands(config.PreMountCommands, ctx, state); err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepPreMountCommands) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,46 @@
package hyperone
import (
"context"
"fmt"
"log"
"os"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepPrepareDevice struct{}
func (s *stepPrepareDevice) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
chrootDiskID := state.Get("chroot_disk_id").(string)
var err error
log.Println("Searching for available device...")
device, err := availableDevice(chrootDiskID)
if err != nil {
err := fmt.Errorf("error finding available device: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if _, err := os.Stat(device); err == nil {
err := fmt.Errorf("device is in use: %s", device)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Found device: %s", device))
state.Put("device", device)
return multistep.ActionContinue
}
func (s *stepPrepareDevice) Cleanup(state multistep.StateBag) {}
func availableDevice(scsiID string) (string, error) {
// TODO proper SCSI search
return "/dev/sdb", nil
}

View File

@ -3,6 +3,9 @@ package hyperone
import ( import (
"fmt" "fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/hyperonecom/h1-client-go" "github.com/hyperonecom/h1-client-go"
) )
@ -14,3 +17,40 @@ func formatOpenAPIError(err error) string {
return fmt.Sprintf("%s (body: %s)", openAPIError.Error(), openAPIError.Body()) return fmt.Sprintf("%s (body: %s)", openAPIError.Error(), openAPIError.Body())
} }
func runCommands(commands []string, ctx interpolate.Context, state multistep.StateBag) error {
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
comm := state.Get("communicator").(packer.Communicator)
for _, rawCmd := range commands {
intCmd, err := interpolate.Render(rawCmd, &ctx)
if err != nil {
return fmt.Errorf("error interpolating: %s", err)
}
command, err := wrappedCommand(intCmd)
if err != nil {
return fmt.Errorf("error wrapping command: %s", err)
}
remoteCmd := &packer.RemoteCmd{
Command: command,
}
ui.Say(fmt.Sprintf("Executing command: %s", command))
err = remoteCmd.StartWithUi(comm, ui)
if err != nil {
return fmt.Errorf("error running remote cmd: %s", err)
}
if remoteCmd.ExitStatus != 0 {
return fmt.Errorf(
"received non-zero exit code %d from command: %s",
remoteCmd.ExitStatus,
command)
}
}
return nil
}

View File

@ -0,0 +1,29 @@
{
"variables": {
"token": "{{ env `HYPERONE_TOKEN` }}",
"project": "{{ env `HYPERONE_PROJECT` }}"
},
"builders": [
{
"token": "{{ user `token` }}",
"project": "{{ user `project` }}",
"type": "hyperone",
"source_image": "ubuntu",
"disk_size": 10,
"vm_type": "a1.nano",
"chroot_disk": true,
"chroot_command_wrapper": "sudo {{.Command}}",
"pre_mount_commands": [
"parted {{.Device}} mklabel msdos mkpart primary 1M 100% set 1 boot on print",
"mkfs.ext4 {{.Device}}1"
],
"post_mount_commands": [
"apt-get update",
"apt-get install debootstrap",
"debootstrap --arch amd64 bionic {{.MountPath}}"
]
}
],
"provisioners": []
}

View File

@ -114,7 +114,7 @@ builder.
- `disk_name` (string) - The name of the created disk. - `disk_name` (string) - The name of the created disk.
- `disk_type` (string) - The type of the created disk. - `disk_type` (string) - The type of the created disk. Defaults to `ssd`.
- `image_description` (string) - The description of the resulting image. - `image_description` (string) - The description of the resulting image.
@ -154,6 +154,57 @@ builder.
- `vm_tags` (map of key/value strings) - Key/value pair tags to - `vm_tags` (map of key/value strings) - Key/value pair tags to
add to the created server. add to the created server.
## Chroot disk
### Required:
- `chroot_disk` (bool) - Set to `true` to enable chroot disk build.
- `pre_mount_commands` (array of strings) - A series of commands to execute
before mounting the chroot. This should include any partitioning and
filesystem creation commands. The path to the device is provided by
`{{.Device}}`.
### Optional:
- `chroot_command_wrapper` (string) - How to run shell commands. This defaults
to `{{.Command}}`. This may be useful to set if you want to set
environment variables or run commands with `sudo`.
- `chroot_copy_files` (array of strings) - Paths to files on the running VM
that will be copied into the chroot environment before provisioning.
Defaults to `/etc/resolv.conf` so that DNS lookups work.
- `chroot_disk_size` (float) - The size of the chroot disk in GiB. Defaults
to `disk_size`.
- `chroot_disk_type` (string) - The type of the chroot disk. Defaults to
`disk_type`.
- `chroot_mount_path` (string) - The path on which the device will be mounted.
- `chroot_mounts` (array of strings) - A list of devices to mount into the
chroot environment. This is a list of 3-element tuples, in order:
- The filesystem type. If this is "bind", then Packer will properly bind the
filesystem to another mount point.
- The source device.
- The mount directory.
- `mount_options` (array of tuples) - Options to supply the `mount` command
when mounting devices. Each option will be prefixed with `-o` and supplied
to the `mount` command.
- `mount_partition` (string) - The partition number containing the / partition.
By default this is the first partition of the volume (for example, sdb1).
- `post_mount_commands` (array of strings) - 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}}`.
## Basic Example ## Basic Example
Here is a basic example. It is completely valid as soon as you enter your own Here is a basic example. It is completely valid as soon as you enter your own
@ -161,10 +212,29 @@ token.
``` json ``` json
{ {
"type": "hyperone", "type": "hyperone",
"token": "YOUR_AUTH_TOKEN", "token": "YOUR_AUTH_TOKEN",
"source_image": "ubuntu-18.04", "source_image": "ubuntu-18.04",
"vm_flavour": "a1.nano", "vm_flavour": "a1.nano",
"disk_size": 10 "disk_size": 10
}
```
## Chroot Example
``` json
{
"type": "hyperone",
"token": "YOUR_AUTH_TOKEN",
"source_image": "ubuntu-18.04",
"vm_flavour": "a1.nano",
"disk_size": 10,
"chroot_disk": true,
"pre_mount_commands": [
"apt-get update",
"apt-get install debootstrap",
"debootstrap --arch amd64 bionic {{.MountPath}}"
]
} }
``` ```