internal/goalstate/goalstate.go (96 lines of code) (raw):
package goalstate
import (
"fmt"
"time"
"github.com/Azure/run-command-handler-linux/internal/cleanup"
commands "github.com/Azure/run-command-handler-linux/internal/cmds"
"github.com/Azure/run-command-handler-linux/internal/commandProcessor"
"github.com/Azure/run-command-handler-linux/internal/constants"
"github.com/Azure/run-command-handler-linux/internal/handlersettings"
"github.com/Azure/run-command-handler-linux/internal/observer"
"github.com/Azure/run-command-handler-linux/internal/settings"
"github.com/Azure/run-command-handler-linux/internal/status"
"github.com/Azure/run-command-handler-linux/internal/types"
"github.com/go-kit/kit/log"
"github.com/pkg/errors"
)
const (
enableCommand string = "enable"
maxExecutionTimeInMinutes int32 = 90
)
// HandleImmediateGoalState handles the immediate goal state by executing the command and waiting for it to finish.
// ctx: The logger context.
// setting: The settings for the command.
// notifier: The notifier to send the status to the HGAP. This is a notifier that must have been initialized and a status observer must have been added to it.
// Returns the exit code and an error if there was an issue executing the goal state.
func HandleImmediateGoalState(ctx *log.Context, setting settings.SettingsCommon, notifier *observer.Notifier) (int, error) {
done := make(chan bool)
err := make(chan error)
go startAsync(ctx, setting, notifier, done, err)
select {
case <-err:
return constants.ExitCode_ImmediateTaskFailed, errors.Wrapf(<-err, "error when trying to execute goal state")
case <-done:
ctx.Log("message", "goal state successfully finished")
return constants.ExitCode_Okay, nil
case <-time.After(time.Minute * time.Duration(maxExecutionTimeInMinutes)):
return constants.ExitCode_ImmediateTaskTimeout, errors.New("timeout when trying to execute goal state")
}
}
// ReportFinalStatusForImmediateGoalState reports the final status of the immediate goal state to the HGAP.
// Reporting the status to HGAP is done by notifying the observer previously added to the notifier.
// This function is called when the goal state is skipped or when the goal state fails to execute.
// It is important to get the instance view from the goal state and report it to the HGAP so that the user can see the final status of the goal state.
// The instance view is normally added as a message to the status item.
func ReportFinalStatusForImmediateGoalState(ctx *log.Context, notifier *observer.Notifier, goalStateKey types.GoalStateKey, statusType types.StatusType, instanceview *types.RunCommandInstanceView) error {
if notifier == nil {
return errors.New("notifier is nil. Cannot report status to HGAP")
}
cmd, ok := commands.Cmds[enableCommand]
if !ok {
return errors.New("missing enable command")
}
if !cmd.ShouldReportStatus {
ctx.Log("status", "status not reported for operation (by design)")
return nil
}
msg, err := instanceview.Marshal()
if err != nil {
return errors.Wrapf(err, "failed to marshal instance view")
}
statusItem, err := status.GetSingleStatusItem(ctx, statusType, cmd, string(msg))
if err != nil {
return errors.Wrap(err, "failed to get status item")
}
ctx.Log("message", "reporting status of skipped goal state by notifying the observer to then send to HGAP")
return notifier.Notify(types.StatusEventArgs{
StatusKey: goalStateKey,
TopLevelStatus: statusItem,
})
}
// startAsync starts the command asynchronously. The command to execute is the enable command.
// The function to report status is overwritten to report the status to the HGAP. The notifier is used to send the status to the HGAP.
// The function to cleanup the command is overwritten to cleanup the command immediately after it has been executed.
func startAsync(ctx *log.Context, setting settings.SettingsCommon, notifier *observer.Notifier, done chan bool, err chan error) {
if notifier == nil {
err <- errors.New("notifier is nil. Cannot report status to HGAP")
return
}
cmd, ok := commands.Cmds[enableCommand]
if !ok {
err <- errors.New("missing enable command")
return
}
// Overwrite function to report status to HGAP. This function prepares the status to be sent to the HGAP and then calls the notifier to send it.
cmd.Functions.ReportStatus = func(ctx *log.Context, _ types.HandlerEnvironment, metadata types.RCMetadata, statusType types.StatusType, c types.Cmd, msg string) error {
if !c.ShouldReportStatus {
ctx.Log("status", "not reported for operation (by design)")
return nil
}
statusItem, err := status.GetSingleStatusItem(ctx, statusType, c, msg)
if err != nil {
return errors.Wrap(err, "failed to get status item")
}
ctx.Log("message", fmt.Sprintf("reporting status by notifying the observer to then send to HGAP for extension name %v and seq number %v", metadata.ExtName, metadata.SeqNum))
return notifier.Notify(types.StatusEventArgs{
StatusKey: types.GoalStateKey{
ExtensionName: metadata.ExtName,
SeqNumber: metadata.SeqNum,
},
TopLevelStatus: statusItem,
})
}
// Overwrite function to cleanup the command. This function is called after the command has been executed.
cmd.Functions.Cleanup = cleanup.ImmediateRunCommandCleanup
var hs handlersettings.HandlerSettingsFile
var runtimeSettings []handlersettings.RunTimeSettingsFile
hs.RuntimeSettings = append(runtimeSettings, handlersettings.RunTimeSettingsFile{HandlerSettings: setting})
ctx.Log("message", "executing immediate goal state")
commandProcessor.ProcessImmediateHandlerCommand(cmd, hs, *setting.ExtensionName, *setting.SeqNo)
done <- true
}