From 9afaa5a21f4f34f1708ca02b1d1ea18512ff049e Mon Sep 17 00:00:00 2001 From: mflorin Date: Tue, 9 Feb 2021 17:56:06 +0200 Subject: [PATCH] Profitbricks builder fixes (#10549) --- builder/profitbricks/step_create_server.go | 13 +- builder/profitbricks/step_take_snapshot.go | 132 +++++++++++++++++++-- go.sum | 1 + 3 files changed, 135 insertions(+), 11 deletions(-) diff --git a/builder/profitbricks/step_create_server.go b/builder/profitbricks/step_create_server.go index d7e19466e..f8a7667dd 100644 --- a/builder/profitbricks/step_create_server.go +++ b/builder/profitbricks/step_create_server.go @@ -22,8 +22,11 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu profitbricks.SetAuth(c.PBUsername, c.PBPassword) profitbricks.SetDepth("5") - if sshkey, ok := state.GetOk("publicKey"); ok { - c.SSHKey = sshkey.(string) + 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) @@ -204,7 +207,7 @@ func (d *stepCreateServer) setPB(username string, password string, url string) { func (d *stepCreateServer) checkForErrors(instance profitbricks.Resp) error { if instance.StatusCode > 299 { - return errors.New(fmt.Sprintf("Error occurred %s", string(instance.Body))) + return fmt.Errorf("Error occurred %s", string(instance.Body)) } return nil } @@ -261,7 +264,9 @@ func (d *stepCreateServer) getImageAlias(imageAlias string, location string, ui func parseErrorMessage(raw string) (toreturn string) { var tmp map[string]interface{} - json.Unmarshal([]byte(raw), &tmp) + if json.Unmarshal([]byte(raw), &tmp) != nil { + return "" + } for _, v := range tmp["messages"].([]interface{}) { for index, i := range v.(map[string]interface{}) { diff --git a/builder/profitbricks/step_take_snapshot.go b/builder/profitbricks/step_take_snapshot.go index b038fc021..962341b8e 100644 --- a/builder/profitbricks/step_take_snapshot.go +++ b/builder/profitbricks/step_take_snapshot.go @@ -3,6 +3,9 @@ package profitbricks import ( "context" "encoding/json" + "errors" + "fmt" + "strings" "time" "github.com/hashicorp/packer-plugin-sdk/multistep" @@ -22,9 +25,32 @@ func (s *stepTakeSnapshot) Run(ctx context.Context, state multistep.StateBag) mu 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 { @@ -42,31 +68,123 @@ func (s *stepTakeSnapshot) Run(ctx context.Context, state multistep.StateBag) mu return multistep.ActionHalt } - s.waitTillProvisioned(snapshot.Headers.Get("Location"), *c) + 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(state multistep.StateBag) { +func (s *stepTakeSnapshot) Cleanup(_ multistep.StateBag) { } -func (d *stepTakeSnapshot) waitTillProvisioned(path string, config Config) { - d.setPB(config.PBUsername, config.PBPassword, config.PBUrl) +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 } - time.Sleep(10 * time.Second) + 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 (d *stepTakeSnapshot) setPB(username string, password string, url string) { +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) } diff --git a/go.sum b/go.sum index 62e6aedbd..3fc023cc4 100644 --- a/go.sum +++ b/go.sum @@ -530,6 +530,7 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZX github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI= github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=