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/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)
|
||||||
|
|
|
@ -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"`
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
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)
|
||||||
|
|
|
@ -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 (
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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_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}}"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
Loading…
Reference in New Issue