280 lines
7.6 KiB
Go
280 lines
7.6 KiB
Go
package profitbricks
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
"github.com/profitbricks/profitbricks-sdk-go"
|
|
)
|
|
|
|
type stepCreateServer struct{}
|
|
|
|
func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
ui := state.Get("ui").(packersdk.Ui)
|
|
c := state.Get("config").(*Config)
|
|
|
|
profitbricks.SetAuth(c.PBUsername, c.PBPassword)
|
|
profitbricks.SetDepth("5")
|
|
if c.Comm.SSHPublicKey != nil {
|
|
c.SSHKey = string(c.Comm.SSHPublicKey)
|
|
} else {
|
|
ui.Error("No ssh private key set; ssh authentication won't be possible. Please specify your private key in the ssh_private_key_file configuration key.")
|
|
return multistep.ActionHalt
|
|
}
|
|
ui.Say("Creating Virtual Data Center...")
|
|
img := s.getImageId(c.Image, c)
|
|
alias := ""
|
|
if img == "" {
|
|
alias = s.getImageAlias(c.Image, c.Region, ui)
|
|
}
|
|
|
|
datacenter := profitbricks.Datacenter{
|
|
Properties: profitbricks.DatacenterProperties{
|
|
Name: c.SnapshotName,
|
|
Location: c.Region,
|
|
},
|
|
}
|
|
server := profitbricks.Server{
|
|
Properties: profitbricks.ServerProperties{
|
|
Name: c.SnapshotName,
|
|
Ram: c.Ram,
|
|
Cores: c.Cores,
|
|
},
|
|
Entities: &profitbricks.ServerEntities{
|
|
Volumes: &profitbricks.Volumes{
|
|
Items: []profitbricks.Volume{
|
|
{
|
|
Properties: profitbricks.VolumeProperties{
|
|
Type: c.DiskType,
|
|
Size: c.DiskSize,
|
|
Name: c.SnapshotName,
|
|
ImageAlias: alias,
|
|
Image: img,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
if c.SSHKey != "" {
|
|
server.Entities.Volumes.Items[0].Properties.SshKeys = []string{c.SSHKey}
|
|
}
|
|
|
|
if c.Comm.SSHPassword != "" {
|
|
server.Entities.Volumes.Items[0].Properties.ImagePassword = c.Comm.SSHPassword
|
|
}
|
|
|
|
datacenter = profitbricks.CompositeCreateDatacenter(datacenter)
|
|
if datacenter.StatusCode > 299 {
|
|
if datacenter.StatusCode > 299 {
|
|
var restError RestError
|
|
err := json.Unmarshal([]byte(datacenter.Response), &restError)
|
|
if err != nil {
|
|
ui.Error(fmt.Sprintf("Error decoding json response: %s", err.Error()))
|
|
return multistep.ActionHalt
|
|
}
|
|
if len(restError.Messages) > 0 {
|
|
ui.Error(restError.Messages[0].Message)
|
|
} else {
|
|
ui.Error(datacenter.Response)
|
|
}
|
|
return multistep.ActionHalt
|
|
}
|
|
}
|
|
|
|
err := s.waitTillProvisioned(datacenter.Headers.Get("Location"), *c)
|
|
if err != nil {
|
|
ui.Error(fmt.Sprintf("Error occurred while creating a datacenter %s", err.Error()))
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
state.Put("datacenter_id", datacenter.Id)
|
|
|
|
server = profitbricks.CreateServer(datacenter.Id, server)
|
|
if server.StatusCode > 299 {
|
|
ui.Error(fmt.Sprintf("Error occurred %s", parseErrorMessage(server.Response)))
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
err = s.waitTillProvisioned(server.Headers.Get("Location"), *c)
|
|
if err != nil {
|
|
ui.Error(fmt.Sprintf("Error occurred while creating a server %s", err.Error()))
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
lan := profitbricks.CreateLan(datacenter.Id, profitbricks.CreateLanRequest{
|
|
Properties: profitbricks.CreateLanProperties{
|
|
Public: true,
|
|
Name: c.SnapshotName,
|
|
},
|
|
})
|
|
|
|
if lan.StatusCode > 299 {
|
|
ui.Error(fmt.Sprintf("Error occurred %s", parseErrorMessage(lan.Response)))
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
err = s.waitTillProvisioned(lan.Headers.Get("Location"), *c)
|
|
if err != nil {
|
|
ui.Error(fmt.Sprintf("Error occurred while creating a LAN %s", err.Error()))
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
lanId, _ := strconv.Atoi(lan.Id)
|
|
nic := profitbricks.CreateNic(datacenter.Id, server.Id, profitbricks.Nic{
|
|
Properties: &profitbricks.NicProperties{
|
|
Name: c.SnapshotName,
|
|
Lan: lanId,
|
|
Dhcp: true,
|
|
},
|
|
})
|
|
|
|
if lan.StatusCode > 299 {
|
|
ui.Error(fmt.Sprintf("Error occurred %s", parseErrorMessage(nic.Response)))
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
err = s.waitTillProvisioned(nic.Headers.Get("Location"), *c)
|
|
if err != nil {
|
|
ui.Error(fmt.Sprintf("Error occurred while creating a NIC %s", err.Error()))
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
state.Put("volume_id", server.Entities.Volumes.Items[0].Id)
|
|
|
|
server = profitbricks.GetServer(datacenter.Id, server.Id)
|
|
// instance_id is the generic term used so that users can have access to the
|
|
// instance id inside of the provisioners, used in step_provision.
|
|
state.Put("instance_id", server.Id)
|
|
|
|
state.Put("server_ip", server.Entities.Nics.Items[0].Properties.Ips[0])
|
|
|
|
return multistep.ActionContinue
|
|
}
|
|
|
|
func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
|
|
c := state.Get("config").(*Config)
|
|
ui := state.Get("ui").(packersdk.Ui)
|
|
|
|
ui.Say("Removing Virtual Data Center...")
|
|
|
|
profitbricks.SetAuth(c.PBUsername, c.PBPassword)
|
|
|
|
if dcId, ok := state.GetOk("datacenter_id"); ok {
|
|
resp := profitbricks.DeleteDatacenter(dcId.(string))
|
|
if err := s.checkForErrors(resp); err != nil {
|
|
ui.Error(fmt.Sprintf(
|
|
"Error deleting Virtual Data Center. Please destroy it manually: %s", err))
|
|
}
|
|
if err := s.waitTillProvisioned(resp.Headers.Get("Location"), *c); err != nil {
|
|
ui.Error(fmt.Sprintf(
|
|
"Error deleting Virtual Data Center. Please destroy it manually: %s", err))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *stepCreateServer) waitTillProvisioned(path string, config Config) error {
|
|
d.setPB(config.PBUsername, config.PBPassword, config.PBUrl)
|
|
waitCount := 120
|
|
if config.Retries > 0 {
|
|
waitCount = config.Retries
|
|
}
|
|
for i := 0; i < waitCount; i++ {
|
|
request := profitbricks.GetRequestStatus(path)
|
|
if request.Metadata.Status == "DONE" {
|
|
return nil
|
|
}
|
|
if request.Metadata.Status == "FAILED" {
|
|
return errors.New(request.Metadata.Message)
|
|
}
|
|
time.Sleep(1 * time.Second)
|
|
i++
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *stepCreateServer) setPB(username string, password string, url string) {
|
|
profitbricks.SetAuth(username, password)
|
|
profitbricks.SetEndpoint(url)
|
|
}
|
|
|
|
func (d *stepCreateServer) checkForErrors(instance profitbricks.Resp) error {
|
|
if instance.StatusCode > 299 {
|
|
return fmt.Errorf("Error occurred %s", string(instance.Body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type RestError struct {
|
|
HttpStatus int `json:"httpStatus,omitempty"`
|
|
Messages []Message `json:"messages,omitempty"`
|
|
}
|
|
|
|
type Message struct {
|
|
ErrorCode string `json:"errorCode,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
func (d *stepCreateServer) getImageId(imageName string, c *Config) string {
|
|
d.setPB(c.PBUsername, c.PBPassword, c.PBUrl)
|
|
|
|
images := profitbricks.ListImages()
|
|
|
|
for i := 0; i < len(images.Items); i++ {
|
|
imgName := ""
|
|
if images.Items[i].Properties.Name != "" {
|
|
imgName = images.Items[i].Properties.Name
|
|
}
|
|
diskType := c.DiskType
|
|
if c.DiskType == "SSD" {
|
|
diskType = "HDD"
|
|
}
|
|
if imgName != "" && strings.Contains(strings.ToLower(imgName), strings.ToLower(imageName)) && images.Items[i].Properties.ImageType == diskType && images.Items[i].Properties.Location == c.Region && images.Items[i].Properties.Public == true {
|
|
return images.Items[i].Id
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (d *stepCreateServer) getImageAlias(imageAlias string, location string, ui packersdk.Ui) string {
|
|
if imageAlias == "" {
|
|
return ""
|
|
}
|
|
locations := profitbricks.GetLocation(location)
|
|
if len(locations.Properties.ImageAliases) > 0 {
|
|
for _, i := range locations.Properties.ImageAliases {
|
|
alias := ""
|
|
if i != "" {
|
|
alias = i
|
|
}
|
|
if alias != "" && strings.EqualFold(alias, imageAlias) {
|
|
return alias
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func parseErrorMessage(raw string) (toreturn string) {
|
|
var tmp map[string]interface{}
|
|
if json.Unmarshal([]byte(raw), &tmp) != nil {
|
|
return ""
|
|
}
|
|
|
|
for _, v := range tmp["messages"].([]interface{}) {
|
|
for index, i := range v.(map[string]interface{}) {
|
|
if index == "message" {
|
|
toreturn = toreturn + i.(string) + "\n"
|
|
}
|
|
}
|
|
}
|
|
return toreturn
|
|
}
|