Add HyperOne builder
This commit is contained in:
parent
9cbeae2c5a
commit
be30415395
|
@ -0,0 +1,48 @@
|
|||
package hyperone
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperonecom/h1-client-go"
|
||||
)
|
||||
|
||||
type Artifact struct {
|
||||
imageName string
|
||||
imageID string
|
||||
client *openapi.APIClient
|
||||
}
|
||||
|
||||
func (a *Artifact) BuilderId() string {
|
||||
return BuilderID
|
||||
}
|
||||
|
||||
func (a *Artifact) Files() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
return a.imageID
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
return fmt.Sprintf("Image '%s' created, ID: %s", a.imageName, a.imageID)
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Destroy() error {
|
||||
if a.imageID == "" {
|
||||
// No image to destroy
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := a.client.ImageApi.ImageDelete(context.TODO(), a.imageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package hyperone
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hyperonecom/h1-client-go"
|
||||
)
|
||||
|
||||
const BuilderID = "hyperone.builder"
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
client *openapi.APIClient
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
config, warnings, errs := NewConfig(raws...)
|
||||
if errs != nil {
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
b.config = *config
|
||||
|
||||
cfg := openapi.NewConfiguration()
|
||||
cfg.AddDefaultHeader("x-auth-token", b.config.Token)
|
||||
if b.config.Project != "" {
|
||||
cfg.AddDefaultHeader("x-project", b.config.Project)
|
||||
}
|
||||
|
||||
prefer := fmt.Sprintf("respond-async,wait=%d", int(b.config.StateTimeout.Seconds()))
|
||||
cfg.AddDefaultHeader("Prefer", prefer)
|
||||
|
||||
b.client = openapi.NewAPIClient(cfg)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
state := &multistep.BasicStateBag{}
|
||||
state.Put("config", &b.config)
|
||||
state.Put("client", b.client)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
steps := []multistep.Step{
|
||||
&stepCreateSSHKey{},
|
||||
&stepCreateVM{},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
Host: getPublicIP,
|
||||
SSHConfig: b.config.Comm.SSHConfigFunc(),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&stepStopVM{},
|
||||
&stepCreateImage{},
|
||||
}
|
||||
|
||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(state)
|
||||
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
artifact := &Artifact{
|
||||
imageID: state.Get("image_id").(string),
|
||||
imageName: state.Get("image_name").(string),
|
||||
client: b.client,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
package hyperone
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/json"
|
||||
"github.com/hashicorp/packer/common/uuid"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
const (
|
||||
configPath = "~/.h1-cli/conf.json"
|
||||
tokenEnv = "HYPERONE_TOKEN"
|
||||
|
||||
defaultDiskType = "ssd"
|
||||
defaultImageService = "564639bc052c084e2f2e3266"
|
||||
defaultStateTimeout = 5 * time.Minute
|
||||
defaultUserName = "guru"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
Token string `mapstructure:"token"`
|
||||
Project string `mapstructure:"project"`
|
||||
TokenLogin string `mapstructure:"token_login"`
|
||||
|
||||
StateTimeout time.Duration `mapstructure:"state_timeout"`
|
||||
|
||||
SourceImage string `mapstructure:"source_image"`
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
ImageDescription string `mapstructure:"image_description"`
|
||||
ImageTags map[string]interface{} `mapstructure:"image_tags"`
|
||||
ImageService string `mapstructure:"image_service"`
|
||||
|
||||
VmFlavour string `mapstructure:"vm_flavour"`
|
||||
VmName string `mapstructure:"vm_name"`
|
||||
VmTags map[string]interface{} `mapstructure:"vm_tags"`
|
||||
|
||||
DiskName string `mapstructure:"disk_name"`
|
||||
DiskType string `mapstructure:"disk_type"`
|
||||
DiskSize float32 `mapstructure:"disk_size"`
|
||||
|
||||
Network string `mapstructure:"network"`
|
||||
PrivateIP string `mapstructure:"private_ip"`
|
||||
PublicIP string `mapstructure:"public_ip"`
|
||||
|
||||
SSHKeys []string `mapstructure:"ssh_keys"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
c := &Config{}
|
||||
|
||||
var md mapstructure.Metadata
|
||||
err := config.Decode(c, &config.DecodeOpts{
|
||||
Metadata: &md,
|
||||
Interpolate: true,
|
||||
InterpolateContext: &c.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"run_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cliConfig, err := loadCLIConfig()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if c.Comm.SSHUsername == "" {
|
||||
c.Comm.SSHUsername = defaultUserName
|
||||
}
|
||||
|
||||
if c.Comm.SSHTimeout == 0 {
|
||||
c.Comm.SSHTimeout = 10 * time.Minute
|
||||
}
|
||||
|
||||
if c.Token == "" {
|
||||
c.Token = os.Getenv(tokenEnv)
|
||||
|
||||
if c.Token == "" {
|
||||
c.Token = cliConfig.Profile.APIKey
|
||||
}
|
||||
|
||||
if c.TokenLogin != "" {
|
||||
c.Token, err = fetchTokenBySSH(c.TokenLogin)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Project == "" {
|
||||
c.Project = cliConfig.Profile.Project.ID
|
||||
}
|
||||
|
||||
if c.StateTimeout == 0 {
|
||||
c.StateTimeout = defaultStateTimeout
|
||||
}
|
||||
|
||||
if c.ImageName == "" {
|
||||
name, err := interpolate.Render("packer-{{timestamp}}", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
c.ImageName = name
|
||||
}
|
||||
|
||||
if c.ImageService == "" {
|
||||
c.ImageService = defaultImageService
|
||||
}
|
||||
|
||||
if c.VmName == "" {
|
||||
c.VmName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
if c.DiskType == "" {
|
||||
c.DiskType = defaultDiskType
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
|
||||
if c.Token == "" {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New("token is required"))
|
||||
}
|
||||
|
||||
if c.VmFlavour == "" {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New("vm flavour is required"))
|
||||
}
|
||||
|
||||
if c.DiskSize == 0 {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New("disk size is required"))
|
||||
}
|
||||
|
||||
if c.SourceImage == "" {
|
||||
errs = packer.MultiErrorAppend(errs, errors.New("source image is required"))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(c.Token)
|
||||
|
||||
return c, nil, nil
|
||||
}
|
||||
|
||||
type cliConfig struct {
|
||||
Profile struct {
|
||||
APIKey string `json:"apiKey"`
|
||||
Project struct {
|
||||
ID string `json:"_id"`
|
||||
} `json:"project"`
|
||||
} `json:"profile"`
|
||||
}
|
||||
|
||||
func loadCLIConfig() (cliConfig, error) {
|
||||
path, err := homedir.Expand(configPath)
|
||||
if err != nil {
|
||||
return cliConfig{}, err
|
||||
}
|
||||
|
||||
_, err = os.Stat(path)
|
||||
if err != nil {
|
||||
// Config not found
|
||||
return cliConfig{}, nil
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return cliConfig{}, err
|
||||
}
|
||||
|
||||
var c cliConfig
|
||||
err = json.Unmarshal(content, &c)
|
||||
if err != nil {
|
||||
return cliConfig{}, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getPublicIP(state multistep.StateBag) (string, error) {
|
||||
return state.Get("public_ip").(string), nil
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package hyperone
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hyperonecom/h1-client-go"
|
||||
)
|
||||
|
||||
type stepCreateImage struct{}
|
||||
|
||||
func (s *stepCreateImage) 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)
|
||||
vmID := state.Get("vm_id").(string)
|
||||
|
||||
ui.Say("Creating image...")
|
||||
|
||||
image, _, err := client.ImageApi.ImageCreate(ctx, openapi.ImageCreate{
|
||||
Name: config.ImageName,
|
||||
Vm: vmID,
|
||||
Service: config.ImageService,
|
||||
Description: config.ImageDescription,
|
||||
Tag: config.ImageTags,
|
||||
})
|
||||
if err != nil {
|
||||
err := fmt.Errorf("error creating image: %s", formatOpenAPIError(err))
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("image_id", image.Id)
|
||||
state.Put("image_name", image.Name)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateImage) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,82 @@
|
|||
package hyperone
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type stepCreateSSHKey struct {
|
||||
Debug bool
|
||||
DebugKeyPath string
|
||||
}
|
||||
|
||||
func (s *stepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
ui.Say("Creating a temporary ssh key for the VM...")
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("error generating ssh key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
privDER := x509.MarshalPKCS1PrivateKey(priv)
|
||||
privBLK := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: privDER,
|
||||
}
|
||||
|
||||
c.Comm.SSHPrivateKey = pem.EncodeToMemory(&privBLK)
|
||||
|
||||
pub, err := ssh.NewPublicKey(&priv.PublicKey)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("error getting public key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
pubSSHFormat := string(ssh.MarshalAuthorizedKey(pub))
|
||||
|
||||
// Remember public SSH key for future connections
|
||||
state.Put("ssh_public_key", pubSSHFormat)
|
||||
|
||||
// If we're in debug mode, output the private key to the working directory.
|
||||
if s.Debug {
|
||||
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
||||
f, err := os.Create(s.DebugKeyPath)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Write the key out
|
||||
if _, err := f.Write(pem.EncodeToMemory(&privBLK)); err != nil {
|
||||
state.Put("error", fmt.Errorf("error saving debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Chmod it so that it is SSH ready
|
||||
if runtime.GOOS != "windows" {
|
||||
if err := f.Chmod(0600); err != nil {
|
||||
state.Put("error", fmt.Errorf("error setting permissions of debug key: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,169 @@
|
|||
package hyperone
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hyperonecom/h1-client-go"
|
||||
)
|
||||
|
||||
type stepCreateVM struct {
|
||||
vmID string
|
||||
}
|
||||
|
||||
func (s *stepCreateVM) 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)
|
||||
|
||||
ui.Say("Creating VM...")
|
||||
|
||||
netAdapter := pickNetAdapter(config)
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
Netadp: []openapi.VmCreateNetadp{netAdapter},
|
||||
UserMetadata: config.UserData,
|
||||
Tag: config.VmTags,
|
||||
}
|
||||
|
||||
vm, _, err := client.VmApi.VmCreate(ctx, options)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("error creating VM: %s", formatOpenAPIError(err))
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.vmID = vm.Id
|
||||
state.Put("vm_id", vm.Id)
|
||||
|
||||
hdds, _, err := client.VmApi.VmListHdd(ctx, vm.Id)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("error listing hdd: %s", formatOpenAPIError(err))
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var diskIDs []string
|
||||
for _, hdd := range hdds {
|
||||
diskIDs = append(diskIDs, hdd.Disk.Id)
|
||||
}
|
||||
|
||||
state.Put("disk_ids", diskIDs)
|
||||
|
||||
netadp, _, err := client.VmApi.VmListNetadp(ctx, vm.Id)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("error listing netadp: %s", formatOpenAPIError(err))
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if len(netadp) < 1 {
|
||||
err := fmt.Errorf("no network adapters found")
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
publicIP, err := associatePublicIP(ctx, config, client, netadp[0])
|
||||
if err != nil {
|
||||
err := fmt.Errorf("error associating IP: %s", formatOpenAPIError(err))
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("public_ip", publicIP)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func pickNetAdapter(config *Config) openapi.VmCreateNetadp {
|
||||
if config.Network == "" {
|
||||
if config.PublicIP != "" {
|
||||
return openapi.VmCreateNetadp{
|
||||
Service: "public",
|
||||
Ip: []string{config.PublicIP},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var privateIPs []string
|
||||
|
||||
if config.PrivateIP == "" {
|
||||
privateIPs = nil
|
||||
} else {
|
||||
privateIPs = []string{config.PrivateIP}
|
||||
}
|
||||
|
||||
return openapi.VmCreateNetadp{
|
||||
Service: "private",
|
||||
Network: config.Network,
|
||||
Ip: privateIPs,
|
||||
}
|
||||
}
|
||||
|
||||
return openapi.VmCreateNetadp{
|
||||
Service: "public",
|
||||
}
|
||||
}
|
||||
|
||||
func associatePublicIP(ctx context.Context, config *Config, client *openapi.APIClient, netadp openapi.Netadp) (string, error) {
|
||||
if config.Network == "" || config.PublicIP == "" {
|
||||
// Public IP belongs to attached net adapter
|
||||
return netadp.Ip[0].Address, nil
|
||||
}
|
||||
|
||||
var privateIP string
|
||||
if config.PrivateIP == "" {
|
||||
privateIP = netadp.Ip[0].Id
|
||||
} else {
|
||||
privateIP = config.PrivateIP
|
||||
}
|
||||
|
||||
ip, _, err := client.IpApi.IpActionAssociate(ctx, config.PublicIP, openapi.IpActionAssociate{Ip: privateIP})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ip.Address, nil
|
||||
}
|
||||
|
||||
func (s *stepCreateVM) Cleanup(state multistep.StateBag) {
|
||||
if s.vmID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("client").(*openapi.APIClient)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deleting VM...")
|
||||
|
||||
deleteOptions := openapi.VmDelete{}
|
||||
diskIDs, ok := state.Get("disk_ids").([]string)
|
||||
if ok && len(diskIDs) > 0 {
|
||||
deleteOptions.RemoveDisks = diskIDs
|
||||
}
|
||||
|
||||
_, 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,107 @@
|
|||
package hyperone
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hyperonecom/h1-client-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPickNetAdapter(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Config Config
|
||||
Expected openapi.VmCreateNetadp
|
||||
}{
|
||||
{
|
||||
Name: "no_network",
|
||||
Config: Config{},
|
||||
Expected: openapi.VmCreateNetadp{
|
||||
Service: "public",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "no_network_public_ip",
|
||||
Config: Config{
|
||||
PublicIP: "some-public-ip",
|
||||
},
|
||||
Expected: openapi.VmCreateNetadp{
|
||||
Service: "public",
|
||||
Ip: []string{"some-public-ip"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "no_network_private_ip",
|
||||
Config: Config{
|
||||
PrivateIP: "some-private-ip",
|
||||
},
|
||||
Expected: openapi.VmCreateNetadp{
|
||||
Service: "public",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "no_network_both_ip",
|
||||
Config: Config{
|
||||
PublicIP: "some-public-ip",
|
||||
PrivateIP: "some-private-ip",
|
||||
},
|
||||
Expected: openapi.VmCreateNetadp{
|
||||
Service: "public",
|
||||
Ip: []string{"some-public-ip"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "network_no_ip",
|
||||
Config: Config{
|
||||
Network: "some-network",
|
||||
},
|
||||
Expected: openapi.VmCreateNetadp{
|
||||
Service: "private",
|
||||
Network: "some-network",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "network_public_ip",
|
||||
Config: Config{
|
||||
Network: "some-network",
|
||||
PublicIP: "some-public-ip",
|
||||
},
|
||||
Expected: openapi.VmCreateNetadp{
|
||||
Service: "private",
|
||||
Network: "some-network",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "network_private_ip",
|
||||
Config: Config{
|
||||
Network: "some-network",
|
||||
PrivateIP: "some-private-ip",
|
||||
},
|
||||
Expected: openapi.VmCreateNetadp{
|
||||
Service: "private",
|
||||
Network: "some-network",
|
||||
Ip: []string{"some-private-ip"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "network_both_ip",
|
||||
Config: Config{
|
||||
Network: "some-network",
|
||||
PublicIP: "some-public-ip",
|
||||
PrivateIP: "some-private-ip",
|
||||
},
|
||||
Expected: openapi.VmCreateNetadp{
|
||||
Service: "private",
|
||||
Network: "some-network",
|
||||
Ip: []string{"some-private-ip"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
result := pickNetAdapter(&c.Config)
|
||||
assert.Equal(t, c.Expected, result)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package hyperone
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hyperonecom/h1-client-go"
|
||||
)
|
||||
|
||||
type stepStopVM struct{}
|
||||
|
||||
func (s *stepStopVM) 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)
|
||||
|
||||
ui.Say("Stopping VM...")
|
||||
|
||||
_, _, err := client.VmApi.VmActionStop(ctx, vmID)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("error stopping VM: %s", formatOpenAPIError(err))
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepStopVM) Cleanup(multistep.StateBag) {}
|
|
@ -0,0 +1,73 @@
|
|||
package hyperone
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/hashicorp/packer/common/json"
|
||||
)
|
||||
|
||||
const (
|
||||
sshAddress = "api.hyperone.com:22"
|
||||
sshSubsystem = "rbx-auth"
|
||||
)
|
||||
|
||||
type sshData struct {
|
||||
ID string `json:"_id"`
|
||||
}
|
||||
|
||||
func sshAgent() ssh.AuthMethod {
|
||||
if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
return ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchTokenBySSH(user string) (string, error) {
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: user,
|
||||
Auth: []ssh.AuthMethod{
|
||||
sshAgent(),
|
||||
},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
client, err := ssh.Dial("tcp", sshAddress, sshConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
stdout, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = session.RequestSubsystem(sshSubsystem)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
out, err := ioutil.ReadAll(stdout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var data sshData
|
||||
err = json.Unmarshal(out, &data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return data.ID, nil
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package hyperone
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperonecom/h1-client-go"
|
||||
)
|
||||
|
||||
func formatOpenAPIError(err error) string {
|
||||
openAPIError, ok := err.(openapi.GenericOpenAPIError)
|
||||
if !ok {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s (body: %s)", openAPIError.Error(), openAPIError.Body())
|
||||
}
|
|
@ -26,6 +26,7 @@ import (
|
|||
filebuilder "github.com/hashicorp/packer/builder/file"
|
||||
googlecomputebuilder "github.com/hashicorp/packer/builder/googlecompute"
|
||||
hcloudbuilder "github.com/hashicorp/packer/builder/hcloud"
|
||||
hyperonebuilder "github.com/hashicorp/packer/builder/hyperone"
|
||||
hypervisobuilder "github.com/hashicorp/packer/builder/hyperv/iso"
|
||||
hypervvmcxbuilder "github.com/hashicorp/packer/builder/hyperv/vmcx"
|
||||
lxcbuilder "github.com/hashicorp/packer/builder/lxc"
|
||||
|
@ -99,6 +100,7 @@ var Builders = map[string]packer.Builder{
|
|||
"file": new(filebuilder.Builder),
|
||||
"googlecompute": new(googlecomputebuilder.Builder),
|
||||
"hcloud": new(hcloudbuilder.Builder),
|
||||
"hyperone": new(hyperonebuilder.Builder),
|
||||
"hyperv-iso": new(hypervisobuilder.Builder),
|
||||
"hyperv-vmcx": new(hypervvmcxbuilder.Builder),
|
||||
"lxc": new(lxcbuilder.Builder),
|
||||
|
|
1
go.mod
1
go.mod
|
@ -90,6 +90,7 @@ require (
|
|||
github.com/hashicorp/vault-plugin-secrets-kv v0.0.0-20181106190520-2236f141171e // indirect
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb
|
||||
github.com/hetznercloud/hcloud-go v1.12.0
|
||||
github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775
|
||||
github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee // indirect
|
||||
github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -197,6 +197,8 @@ github.com/hetznercloud/hcloud-go v1.12.0 h1:ugZO8a8ADekqSWi7xWlcs6pxr4QE0tw5Vny
|
|||
github.com/hetznercloud/hcloud-go v1.12.0/go.mod h1:g5pff0YNAZywQaivY/CmhUYFVp7oP0nu3MiODC2W4Hw=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775 h1:MIteIoIQ5nFoOmwEHPDsqng8d0dtKj3lCnQCwGvtxXc=
|
||||
github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775/go.mod h1:R9rU87RxxmcD3DkspW9JqGBXiJyg5MA+WNCtJrBtnXs=
|
||||
github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee h1:AQ/QmCk6x8ECPpf2pkPtA4lyncEEBbs8VFnVXPYKhIs=
|
||||
github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee/go.mod h1:N0t2vlmpe8nyZB5ouIbJQPDSR+mH6oe7xHB9VZHSUzM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
---
|
||||
description: |
|
||||
HyperOne Packer builder creates new images on the HyperOne platform.
|
||||
The builder takes a source image, runs any provisioning necessary on
|
||||
the image after launching it, then creates a reusable image.
|
||||
layout: docs
|
||||
page_title: 'HyperOne - Builders'
|
||||
sidebar_current: 'docs-builders-hyperone'
|
||||
---
|
||||
|
||||
# HyperOne Builder
|
||||
|
||||
Type: `hyperone`
|
||||
|
||||
The `hyperone` Packer builder is able to create new images on the [HyperOne
|
||||
platform](http://www.hyperone.com/). The builder takes a source image, runs
|
||||
any provisioning necessary on the image after launching it, then creates a
|
||||
reusable image.
|
||||
|
||||
The builder does *not* manage images. Once it creates an image, it is up to you
|
||||
to use it or delete it.
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
There are many configuration options available for the builder. They are
|
||||
segmented below into two categories: required and optional parameters. Within
|
||||
each category, the available configuration keys are alphabetized.
|
||||
|
||||
In addition to the options listed here, a
|
||||
[communicator](/docs/templates/communicator.html) can be configured for this
|
||||
builder.
|
||||
|
||||
### Required:
|
||||
|
||||
- `disk_size` (float) - Size of the created disk, in GiB.
|
||||
|
||||
- `project` (string) - The id or name of the project. This field is required
|
||||
only if using session tokens. It should be skipped when using service
|
||||
account authentication.
|
||||
|
||||
- `source_image` (string) - ID or name of the image to launch server from.
|
||||
|
||||
- `token` (string) - The authentication token used to access your account.
|
||||
This can be either a session token or a service account token.
|
||||
If not defined, the builder will attempt to find it in the following order:
|
||||
|
||||
- In `HYPERONE_TOKEN` environment variable.
|
||||
- In `~/.h1-cli/conf.json` config file used by [h1-cli](https://github.com/hyperonecom/h1-cli).
|
||||
- By using SSH authentication if `token_login` variable has been set.
|
||||
|
||||
- `vm_flavour` (string) - ID or name of the type this server should be created with.
|
||||
|
||||
### Optional:
|
||||
|
||||
- `disk_name` (string) - The name of the created disk.
|
||||
|
||||
- `disk_type` (string) - The type of the created disk.
|
||||
|
||||
- `image_description` (string) - The description of the resulting image.
|
||||
|
||||
- `image_name` (string) - The name of the resulting image. Defaults to
|
||||
"packer-{{timestamp}}"
|
||||
(see [configuration templates](/docs/templates/engine.html) for more info).
|
||||
|
||||
- `image_service` (string) - The service of the resulting image.
|
||||
|
||||
- `image_tags` (map of key/value strings) - Key/value pair tags to
|
||||
add to the created image.
|
||||
|
||||
- `network` (string) - The ID of the network to attach to the created server.
|
||||
|
||||
- `private_ip` (string) - The ID of the private IP within chosen `network`
|
||||
that should be assigned to the created server.
|
||||
|
||||
- `public_ip` (string) - The ID of the public IP that should be assigned to
|
||||
the created server. If `network` is chosen, the public IP will be associated
|
||||
with server's private IP.
|
||||
|
||||
- `ssh_keys` (array of strings) - List of SSH keys by name or id to be added
|
||||
to the server on launch.
|
||||
|
||||
- `state_timeout` (string) - Timeout for waiting on the API to complete
|
||||
a request. Defaults to 5m.
|
||||
|
||||
- `token_login` (string) - Login (an e-mail) on HyperOne platform. Set this
|
||||
if you want to fetch the token by SSH authentication.
|
||||
|
||||
- `user_data` (string) - User data to launch with the server. Packer will not
|
||||
automatically wait for a user script to finish before shutting down the
|
||||
instance, this must be handled in a provisioner.
|
||||
|
||||
- `vm_name` (string) - The name of the created server.
|
||||
|
||||
- `vm_tags` (map of key/value strings) - Key/value pair tags to
|
||||
add to the created server.
|
||||
|
||||
## Basic Example
|
||||
|
||||
Here is a basic example. It is completely valid as soon as you enter your own
|
||||
token.
|
||||
|
||||
``` json
|
||||
{
|
||||
"type": "hyperone",
|
||||
"token": "YOUR_AUTH_TOKEN",
|
||||
"source_image": "ubuntu-18.04",
|
||||
"vm_flavour": "a1.nano",
|
||||
"disk_size": 10
|
||||
}
|
||||
```
|
|
@ -102,6 +102,9 @@
|
|||
<li<%= sidebar_current("docs-builders-hetzner-cloud") %>>
|
||||
<a href="/docs/builders/hetzner-cloud.html">Hetzner Cloud</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-builders-hyperone") %>>
|
||||
<a href="/docs/builders/hyperone.html">HyperOne</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-builders-hyperv") %>>
|
||||
<a href="/docs/builders/hyperv.html">Hyper-V</a>
|
||||
<ul class="nav">
|
||||
|
|
Loading…
Reference in New Issue