2020-12-09 10:51:34 -05:00
|
|
|
package yandexexport
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2020-12-17 16:29:25 -05:00
|
|
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
|
|
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
|
|
"github.com/hashicorp/packer-plugin-sdk/retry"
|
2020-12-09 10:51:34 -05:00
|
|
|
"github.com/hashicorp/packer/builder/yandex"
|
|
|
|
)
|
|
|
|
|
2020-12-10 06:52:22 -05:00
|
|
|
type StepWaitCloudInitScript struct {
|
|
|
|
Tries int
|
|
|
|
}
|
2020-12-09 10:51:34 -05:00
|
|
|
|
|
|
|
type cloudInitStatus struct {
|
|
|
|
V1 struct {
|
|
|
|
Errors []interface{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type cloudInitError struct {
|
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *cloudInitError) Error() string {
|
|
|
|
return e.Err.Error()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run reads the instance metadata and looks for the log entry
|
|
|
|
// indicating the cloud-init script finished.
|
|
|
|
func (s *StepWaitCloudInitScript) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
|
|
|
ui := state.Get("ui").(packersdk.Ui)
|
|
|
|
comm := state.Get("communicator").(packersdk.Communicator)
|
|
|
|
|
|
|
|
ui.Say("Waiting for any running cloud-init script to finish...")
|
|
|
|
|
|
|
|
ctxWithCancel, cancelCtx := context.WithCancel(ctx)
|
|
|
|
|
|
|
|
defer cancelCtx()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
cmd := &packersdk.RemoteCmd{
|
|
|
|
Command: "tail -f /var/log/cloud-init-output.log",
|
|
|
|
}
|
|
|
|
|
|
|
|
err := cmd.RunWithUi(ctxWithCancel, comm, ui)
|
|
|
|
if err != nil && !errors.Is(err, context.Canceled) {
|
|
|
|
ui.Error(err.Error())
|
|
|
|
return
|
|
|
|
}
|
2020-12-10 10:50:28 -05:00
|
|
|
ui.Message("Cloud-init output closed")
|
|
|
|
}()
|
|
|
|
|
|
|
|
// periodically show progress by sending SIGUSR1 to `qemu-img` process
|
|
|
|
go func() {
|
|
|
|
cmd := &packersdk.RemoteCmd{
|
|
|
|
Command: "until pid=$(pidof qemu-img) ; do sleep 1 ; done ; " +
|
|
|
|
"while true ; do sudo kill -s SIGUSR1 ${pid}; sleep 10 ; done",
|
|
|
|
}
|
|
|
|
|
|
|
|
err := cmd.RunWithUi(ctxWithCancel, comm, ui)
|
|
|
|
if err != nil && !errors.Is(err, context.Canceled) {
|
|
|
|
ui.Error("qemu-img signal sender error: " + err.Error())
|
|
|
|
return
|
|
|
|
}
|
2020-12-09 10:51:34 -05:00
|
|
|
}()
|
|
|
|
|
|
|
|
// Keep checking the serial port output to see if the cloud-init script is done.
|
|
|
|
retryConfig := &retry.Config{
|
|
|
|
ShouldRetry: func(e error) bool {
|
|
|
|
switch e.(type) {
|
|
|
|
case *cloudInitError:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
},
|
2020-12-10 06:52:22 -05:00
|
|
|
Tries: s.Tries,
|
2020-12-09 10:51:34 -05:00
|
|
|
RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 60 * time.Second, Multiplier: 2}).Linear,
|
|
|
|
}
|
|
|
|
|
|
|
|
err := retryConfig.Run(ctx, func(ctx context.Context) error {
|
|
|
|
buff := bytes.Buffer{}
|
|
|
|
err := comm.Download("/var/run/cloud-init/result.json", &buff)
|
|
|
|
if err != nil {
|
|
|
|
err := fmt.Errorf("Waiting cloud-init script status: %s", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
result := &cloudInitStatus{}
|
|
|
|
err = json.Unmarshal(buff.Bytes(), result)
|
|
|
|
if err != nil {
|
|
|
|
err := fmt.Errorf("Failed parse result: %s", err)
|
|
|
|
return &cloudInitError{Err: err}
|
|
|
|
}
|
|
|
|
if len(result.V1.Errors) != 0 {
|
|
|
|
err := fmt.Errorf("Result: %v", result.V1.Errors)
|
|
|
|
return &cloudInitError{Err: err}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
err := fmt.Errorf("Error waiting for cloud-init script to finish: %s", err)
|
|
|
|
return yandex.StepHaltWithError(state, err)
|
|
|
|
}
|
|
|
|
ui.Say("Cloud-init script has finished running.")
|
|
|
|
return multistep.ActionContinue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleanup.
|
|
|
|
func (s *StepWaitCloudInitScript) Cleanup(state multistep.StateBag) {}
|