Add chroot disk build
This commit is contained in:
parent
aaee600be0
commit
bd4ce90728
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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{},
|
||||
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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"`
|
||||
|
||||
|
@ -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...)
|
||||
|
@ -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...)
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -3,6 +3,9 @@ package hyperone
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"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())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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": []
|
||||
}
|
|
@ -114,7 +114,7 @@ builder.
|
|||
|
||||
- `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.
|
||||
|
||||
|
@ -154,6 +154,57 @@ builder.
|
|||
- `vm_tags` (map of key/value strings) - Key/value pair tags to
|
||||
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
|
||||
|
||||
Here is a basic example. It is completely valid as soon as you enter your own
|
||||
|
@ -161,10 +212,29 @@ token.
|
|||
|
||||
``` json
|
||||
{
|
||||
"type": "hyperone",
|
||||
"token": "YOUR_AUTH_TOKEN",
|
||||
"source_image": "ubuntu-18.04",
|
||||
"vm_flavour": "a1.nano",
|
||||
"disk_size": 10
|
||||
"type": "hyperone",
|
||||
"token": "YOUR_AUTH_TOKEN",
|
||||
"source_image": "ubuntu-18.04",
|
||||
"vm_flavour": "a1.nano",
|
||||
"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}}"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
Loading…
Reference in New Issue