191 lines
5.2 KiB
Go
191 lines
5.2 KiB
Go
package profitbricks
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
"github.com/profitbricks/profitbricks-sdk-go"
|
|
)
|
|
|
|
type stepTakeSnapshot struct{}
|
|
|
|
func (s *stepTakeSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
ui := state.Get("ui").(packersdk.Ui)
|
|
c := state.Get("config").(*Config)
|
|
|
|
ui.Say("Creating ProfitBricks snapshot...")
|
|
|
|
profitbricks.SetAuth(c.PBUsername, c.PBPassword)
|
|
|
|
dcId := state.Get("datacenter_id").(string)
|
|
volumeId := state.Get("volume_id").(string)
|
|
serverId := state.Get("instance_id").(string)
|
|
|
|
comm, _ := state.Get("communicator").(packersdk.Communicator)
|
|
if comm == nil {
|
|
ui.Error("no communicator found")
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
/* sync fs changes from the provisioning step */
|
|
os, err := s.getOs(dcId, serverId)
|
|
if err != nil {
|
|
ui.Error(fmt.Sprintf("an error occurred while getting the server os: %s", err.Error()))
|
|
return multistep.ActionHalt
|
|
}
|
|
ui.Say(fmt.Sprintf("Server OS is %s", os))
|
|
|
|
switch strings.ToLower(os) {
|
|
case "linux":
|
|
ui.Say("syncing file system changes")
|
|
if err := s.syncFs(ctx, comm); err != nil {
|
|
ui.Error(fmt.Sprintf("error syncing fs changes: %s", err.Error()))
|
|
return multistep.ActionHalt
|
|
}
|
|
}
|
|
|
|
snapshot := profitbricks.CreateSnapshot(dcId, volumeId, c.SnapshotName, "")
|
|
state.Put("snapshotname", c.SnapshotName)
|
|
|
|
if snapshot.StatusCode > 299 {
|
|
var restError RestError
|
|
if err := json.Unmarshal([]byte(snapshot.Response), &restError); err != nil {
|
|
ui.Error(err.Error())
|
|
return multistep.ActionHalt
|
|
}
|
|
if len(restError.Messages) > 0 {
|
|
ui.Error(restError.Messages[0].Message)
|
|
} else {
|
|
ui.Error(snapshot.Response)
|
|
}
|
|
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
ui.Say(fmt.Sprintf("Creating a snapshot for %s/volumes/%s", dcId, volumeId))
|
|
|
|
err = s.waitForRequest(snapshot.Headers.Get("Location"), *c, ui)
|
|
if err != nil {
|
|
ui.Error(fmt.Sprintf("An error occurred while waiting for the request to be done: %s", err.Error()))
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
err = s.waitTillSnapshotAvailable(snapshot.Id, *c, ui)
|
|
if err != nil {
|
|
ui.Error(fmt.Sprintf("An error occurred while waiting for the snapshot to be created: %s", err.Error()))
|
|
return multistep.ActionHalt
|
|
}
|
|
|
|
return multistep.ActionContinue
|
|
}
|
|
|
|
func (s *stepTakeSnapshot) Cleanup(_ multistep.StateBag) {
|
|
}
|
|
|
|
func (s *stepTakeSnapshot) waitForRequest(path string, config Config, ui packersdk.Ui) error {
|
|
|
|
ui.Say(fmt.Sprintf("Watching request %s", path))
|
|
s.setPB(config.PBUsername, config.PBPassword, config.PBUrl)
|
|
waitCount := 50
|
|
var waitInterval = 10 * time.Second
|
|
if config.Retries > 0 {
|
|
waitCount = config.Retries
|
|
}
|
|
done := false
|
|
for i := 0; i < waitCount; i++ {
|
|
request := profitbricks.GetRequestStatus(path)
|
|
ui.Say(fmt.Sprintf("request status = %s", request.Metadata.Status))
|
|
if request.Metadata.Status == "DONE" {
|
|
done = true
|
|
break
|
|
}
|
|
if request.Metadata.Status == "FAILED" {
|
|
return fmt.Errorf("Request failed: %s", request.Response)
|
|
}
|
|
time.Sleep(waitInterval)
|
|
i++
|
|
}
|
|
|
|
if done == false {
|
|
return fmt.Errorf("request not fulfilled after waiting %d seconds",
|
|
int64(waitCount)*int64(waitInterval)/int64(time.Second))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *stepTakeSnapshot) waitTillSnapshotAvailable(id string, config Config, ui packersdk.Ui) error {
|
|
s.setPB(config.PBUsername, config.PBPassword, config.PBUrl)
|
|
waitCount := 50
|
|
var waitInterval = 10 * time.Second
|
|
if config.Retries > 0 {
|
|
waitCount = config.Retries
|
|
}
|
|
done := false
|
|
ui.Say(fmt.Sprintf("waiting for snapshot %s to become available", id))
|
|
for i := 0; i < waitCount; i++ {
|
|
snap := profitbricks.GetSnapshot(id)
|
|
ui.Say(fmt.Sprintf("snapshot status = %s", snap.Metadata.State))
|
|
if snap.StatusCode != 200 {
|
|
return fmt.Errorf("%s", snap.Response)
|
|
}
|
|
if snap.Metadata.State == "AVAILABLE" {
|
|
done = true
|
|
break
|
|
}
|
|
time.Sleep(waitInterval)
|
|
i++
|
|
ui.Say(fmt.Sprintf("... still waiting, %d seconds have passed", int64(waitInterval)*int64(i)))
|
|
}
|
|
|
|
if done == false {
|
|
return fmt.Errorf("snapshot not created after waiting %d seconds",
|
|
int64(waitCount)*int64(waitInterval)/int64(time.Second))
|
|
}
|
|
|
|
ui.Say("snapshot created")
|
|
return nil
|
|
}
|
|
|
|
func (s *stepTakeSnapshot) syncFs(ctx context.Context, comm packersdk.Communicator) error {
|
|
cmd := &packersdk.RemoteCmd{
|
|
Command: "sync",
|
|
}
|
|
if err := comm.Start(ctx, cmd); err != nil {
|
|
return err
|
|
}
|
|
if cmd.Wait() != 0 {
|
|
return fmt.Errorf("sync command exited with code %d", cmd.ExitStatus())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *stepTakeSnapshot) getOs(dcId string, serverId string) (string, error) {
|
|
server := profitbricks.GetServer(dcId, serverId)
|
|
if server.StatusCode != 200 {
|
|
return "", errors.New(server.Response)
|
|
}
|
|
|
|
if server.Properties.BootVolume == nil {
|
|
return "", errors.New("no boot volume found on server")
|
|
}
|
|
|
|
volumeId := server.Properties.BootVolume.Id
|
|
volume := profitbricks.GetVolume(dcId, volumeId)
|
|
if volume.StatusCode != 200 {
|
|
return "", errors.New(volume.Response)
|
|
}
|
|
|
|
return volume.Properties.LicenceType, nil
|
|
}
|
|
|
|
func (s *stepTakeSnapshot) setPB(username string, password string, url string) {
|
|
profitbricks.SetAuth(username, password)
|
|
profitbricks.SetEndpoint(url)
|
|
}
|