mirror of https://github.com/hashicorp/packer
parent
aaee600be0
commit
bd4ce90728
@ -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)
|
||||
}
|
||||
@ -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) {}
|
||||
@ -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
|
||||
}
|
||||
@ -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": []
|
||||
}
|
||||
Loading…
Reference in new issue