Skip to content

Commit

Permalink
Add chroot disk build
Browse files Browse the repository at this point in the history
  • Loading branch information
m110 committed Jan 28, 2019
1 parent aaee600 commit bd4ce90
Show file tree
Hide file tree
Showing 16 changed files with 720 additions and 20 deletions.
37 changes: 34 additions & 3 deletions builder/hyperone/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/hyperonecom/h1-client-go"
)

Expand Down Expand Up @@ -41,12 +42,23 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
return nil, nil
}

type wrappedCommandTemplate struct {
Command string
}

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.Put("config", &b.config)
state.Put("client", b.client)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))

steps := []multistep.Step{
&stepCreateSSHKey{},
Expand All @@ -56,9 +68,28 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Host: getPublicIP,
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)
Expand Down
54 changes: 54 additions & 0 deletions builder/hyperone/chroot_communicator.go
Original file line number Diff line number Diff line change
@@ -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)
}
70 changes: 70 additions & 0 deletions builder/hyperone/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ type Config struct {
PrivateIP string `mapstructure:"private_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"`
UserData string `mapstructure:"user_data"`

Expand All @@ -74,6 +87,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"run_command",
"chroot_command_wrapper",
"post_mount_commands",
"pre_mount_commands",
"mount_path",
},
},
}, raws...)
Expand Down Expand Up @@ -138,6 +155,45 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
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
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
Expand All @@ -159,6 +215,20 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
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 {
return nil, nil, errs
}
Expand Down
28 changes: 28 additions & 0 deletions builder/hyperone/step_chroot_provision.go
Original file line number Diff line number Diff line change
@@ -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) {}
40 changes: 40 additions & 0 deletions builder/hyperone/step_copy_files.go
Original file line number Diff line number Diff line change
@@ -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) {}
40 changes: 29 additions & 11 deletions builder/hyperone/step_create_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ type stepCreateVM struct {
vmID string
}

const (
chrootDiskName = "packer-chroot-disk"
)

func (s *stepCreateVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*openapi.APIClient)
ui := state.Get("ui").(packer.Ui)
Expand All @@ -26,17 +30,27 @@ func (s *stepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis
var sshKeys = []string{sshKey}
sshKeys = append(sshKeys, config.SSHKeys...)

options := openapi.VmCreate{
Name: config.VmName,
Image: config.SourceImage,
Service: config.VmFlavour,
SshKeys: sshKeys,
Disk: []openapi.VmCreateDisk{
{
Service: config.DiskType,
Size: config.DiskSize,
},
disks := []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},
UserMetadata: config.UserData,
Tag: config.VmTags,
Expand All @@ -63,7 +77,11 @@ func (s *stepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis

var diskIDs []string
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)
Expand Down
Loading

0 comments on commit bd4ce90

Please sign in to comment.