func RunCommand()

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
}