in cmd/utils/vmss.go [221:318]
func RunCommand(
ctx context.Context,
cred azcore.TokenCredential,
vm *VirtualMachineScaleSetVM,
command *string,
verbose bool,
timeout *int,
outputTruncate OutputTruncate,
) (
*RunCommandResult,
error,
) {
const (
commandID = "RunShellScript"
initialDelay = 15 * time.Second
pollingFreq = 2 * time.Second
)
if timeout == nil {
timeout = to.IntPtr(DefaultRunCommandTimeoutInSeconds)
}
client, err := armcompute.NewVirtualMachineScaleSetVMsClient(vm.SubscriptionID, cred, nil)
if err != nil {
return nil, fmt.Errorf("creating VMSS VMs client: %w", err)
}
// By default, the Azure API limits the output to the last 4,096 bytes. See
// https://learn.microsoft.com/en-us/azure/virtual-machines/linux/run-command#restrictions.
if outputTruncate == OutputTruncateTail {
*command = fmt.Sprintf("%s | head -c %d", *command, BytesLimit)
}
script := []*string{to.StringPtr(fmt.Sprintf("timeout %d sh -c '%s'", *timeout, *command))}
runCommand := armcompute.RunCommandInput{
CommandID: to.StringPtr(commandID),
Script: script,
}
if verbose {
b, _ := json.MarshalIndent(vm, "", " ")
fmt.Printf("Command: %s\nVirtual Machine Scale Set VM:\n%s\n\n", *command, string(b))
}
s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt, syscall.SIGTERM)
go func() {
<-s
log.Warn("The requested command hasn't finished yet, hit 'Ctrl+C' again to exit anyway.")
log.Warn("However, please notice the command will continue running in the node anyway, " +
"and you will be unable to see the output or run another command until it finishes.")
<-s
os.Exit(1)
}()
DefaultSpinner.Start()
DefaultSpinner.Suffix = " Running..."
poller, err := client.BeginRunCommand(ctx, vm.NodeResourceGroup,
vm.VMScaleSet, vm.InstanceID, runCommand, nil)
if err != nil {
DefaultSpinner.Stop()
return nil, fmt.Errorf("begin running command: %w", err)
}
res, err := poller.PollUntilDone(ctx, &runtime.PollUntilDoneOptions{Frequency: pollingFreq})
DefaultSpinner.Stop()
if err != nil {
return nil, fmt.Errorf("polling command response: %w", err)
}
if verbose {
b, _ := json.MarshalIndent(res, "", " ")
fmt.Printf("\nResponse:\n%s\n", string(b))
}
// TODO: Is it possible to have multiple values after using PollUntilDone()?
if len(res.Value) == 0 || res.Value[0] == nil {
return nil, errors.New("no response received after command execution")
}
val := res.Value[0]
// TODO: Isn't there a constant in the SDK to compare this?
if to.String(val.Code) != "ProvisioningState/succeeded" {
b, _ := json.MarshalIndent(res, "", " ")
return nil, fmt.Errorf("command execution didn't succeed:\n%s", string(b))
}
result, err := parseRunCommandMessage(to.String(val.Message))
if err != nil {
return nil, err
}
if outputTruncate == OutputTruncateTail && result.isTruncated() {
result.Stdout = fmt.Sprintf("%s... (truncated)\n", result.Stdout)
}
return result, nil
}