Esempio n. 1
0
        private async Task <CommandResult> RunAsyncInner(
            IEnumerable <string> args,
            string workingDir,
            IDictionary <string, string?> additionalEnv,
            Action <string>?onStandardOutput    = null,
            Action <string>?onStandardError     = null,
            EventLogFile?eventLogFile           = null,
            CancellationToken cancellationToken = default)
        {
            var stdOutBuffer = new StringBuilder();
            var stdOutPipe   = PipeTarget.ToStringBuilder(stdOutBuffer);

            if (onStandardOutput != null)
            {
                stdOutPipe = PipeTarget.Merge(stdOutPipe, PipeTarget.ToDelegate(onStandardOutput));
            }

            var stdErrBuffer = new StringBuilder();
            var stdErrPipe   = PipeTarget.ToStringBuilder(stdErrBuffer);

            if (onStandardError != null)
            {
                stdErrPipe = PipeTarget.Merge(stdErrPipe, PipeTarget.ToDelegate(onStandardError));
            }

            var pulumiCmd = Cli.Wrap("pulumi")
                            .WithArguments(PulumiArgs(args, eventLogFile), escape: true)
                            .WithWorkingDirectory(workingDir)
                            .WithEnvironmentVariables(PulumiEnvironment(additionalEnv, debugCommands: eventLogFile != null))
                            .WithStandardOutputPipe(stdOutPipe)
                            .WithStandardErrorPipe(stdErrPipe)
                            .WithValidation(CommandResultValidation.None); // we check non-0 exit code ourselves

            var pulumiCmdResult = await pulumiCmd.ExecuteAsync(cancellationToken);

            var result = new CommandResult(
                pulumiCmdResult.ExitCode,
                standardOutput: stdOutBuffer.ToString(),
                standardError: stdErrBuffer.ToString());

            if (pulumiCmdResult.ExitCode != 0)
            {
                throw CommandException.CreateFromResult(result);
            }
            else
            {
                return(result);
            }
        }
Esempio n. 2
0
        public async Task <CommandResult> RunAsync(
            IEnumerable <string> args,
            string workingDir,
            IDictionary <string, string> additionalEnv,
            Action <string>?onStandardOutput    = null,
            Action <string>?onStandardError     = null,
            CancellationToken cancellationToken = default)
        {
            // all commands should be run in non-interactive mode.
            // this causes commands to fail rather than prompting for input (and thus hanging indefinitely)
            var completeArgs = args.Concat(new[] { "--non-interactive" });

            var env = new Dictionary <string, string>();

            foreach (var element in Environment.GetEnvironmentVariables())
            {
                if (element is KeyValuePair <string, object> pair &&
                    pair.Value is string valueStr)
                {
                    env[pair.Key] = valueStr;
                }
            }

            foreach (var pair in additionalEnv)
            {
                env[pair.Key] = pair.Value;
            }

            using var proc = new Process
                  {
                      EnableRaisingEvents = true,
                      StartInfo           = new ProcessStartInfo
                      {
                          FileName               = "pulumi",
                          WorkingDirectory       = workingDir,
                          CreateNoWindow         = true,
                          UseShellExecute        = false,
                          RedirectStandardError  = true,
                          RedirectStandardOutput = true,
                      },
                  };

            foreach (var arg in completeArgs)
            {
                proc.StartInfo.ArgumentList.Add(arg);
            }

            foreach (var pair in env)
            {
                proc.StartInfo.Environment[pair.Key] = pair.Value;
            }

            var standardOutputBuilder = new StringBuilder();

            proc.OutputDataReceived += (_, @event) =>
            {
                if (@event.Data != null)
                {
                    standardOutputBuilder.AppendLine(@event.Data);
                    onStandardOutput?.Invoke(@event.Data);
                }
            };

            var standardErrorBuilder = new StringBuilder();

            proc.ErrorDataReceived += (_, @event) =>
            {
                if (@event.Data != null)
                {
                    standardErrorBuilder.AppendLine(@event.Data);
                    onStandardError?.Invoke(@event.Data);
                }
            };

            var tcs = new TaskCompletionSource <CommandResult>();

            using var cancelRegistration = cancellationToken.Register(() =>
            {
                // if the process has already exited than let's
                // just let it set the result on the task
                if (proc.HasExited || tcs.Task.IsCompleted)
                {
                    return;
                }

                // setting it cancelled before killing so there
                // isn't a race condition to the proc.Exited event
                tcs.TrySetCanceled(cancellationToken);

                try
                {
                    proc.Kill();
                }
                catch
                {
                    // in case the process hasn't started yet
                    // or has already terminated
                }
            });

            proc.Exited += (_, @event) =>
            {
                // this seems odd, since the exit event has been triggered, but
                // the exit event being triggered does not mean that the async
                // output stream handlers have ran to completion. this method
                // doesn't exit until they have, at which point we can be sure
                // we have captured the output in its entirety.
                // note that if we were to pass an explicit wait time to this
                // method it would not wait for the stream handlers.
                // see: https://github.com/dotnet/runtime/issues/18789
                proc.WaitForExit();

                var result = new CommandResult(
                    proc.ExitCode,
                    standardOutputBuilder.ToString(),
                    standardErrorBuilder.ToString());

                if (proc.ExitCode != 0)
                {
                    var ex = CommandException.CreateFromResult(result);
                    tcs.TrySetException(ex);
                }
                else
                {
                    tcs.TrySetResult(result);
                }
            };

            proc.Start();
            proc.BeginOutputReadLine();
            proc.BeginErrorReadLine();
            return(await tcs.Task.ConfigureAwait(false));
        }
Esempio n. 3
0
        public async Task <CommandResult> RunAsync(
            IEnumerable <string> args,
            string workingDir,
            IDictionary <string, string> additionalEnv,
            Action <string>?onOutput            = null,
            CancellationToken cancellationToken = default)
        {
            // all commands should be run in non-interactive mode.
            // this causes commands to fail rather than prompting for input (and thus hanging indefinitely)
            var completeArgs = args.Concat(new[] { "--non-interactive" });

            var env = new Dictionary <string, string>();

            foreach (var element in Environment.GetEnvironmentVariables())
            {
                if (element is KeyValuePair <string, object> pair &&
                    pair.Value is string valueStr)
                {
                    env[pair.Key] = valueStr;
                }
            }

            foreach (var pair in additionalEnv)
            {
                env[pair.Key] = pair.Value;
            }

            using var proc = new Process
                  {
                      EnableRaisingEvents = true,
                      StartInfo           = new ProcessStartInfo
                      {
                          FileName               = "pulumi",
                          WorkingDirectory       = workingDir,
                          CreateNoWindow         = true,
                          UseShellExecute        = false,
                          RedirectStandardError  = true,
                          RedirectStandardOutput = true,
                      },
                  };

            foreach (var arg in completeArgs)
            {
                proc.StartInfo.ArgumentList.Add(arg);
            }

            foreach (var pair in env)
            {
                proc.StartInfo.Environment[pair.Key] = pair.Value;
            }

            var standardOutputBuilder = new StringBuilder();

            proc.OutputDataReceived += (_, @event) =>
            {
                if (@event.Data != null)
                {
                    standardOutputBuilder.AppendLine(@event.Data);
                    onOutput?.Invoke(@event.Data);
                }
            };

            var tcs = new TaskCompletionSource <CommandResult>();

            using var cancelRegistration = cancellationToken.Register(() =>
            {
                // if the process has already exited than let's
                // just let it set the result on the task
                if (proc.HasExited || tcs.Task.IsCompleted)
                {
                    return;
                }

                // setting it cancelled before killing so there
                // isn't a race condition to the proc.Exited event
                tcs.TrySetCanceled(cancellationToken);

                try
                {
                    proc.Kill();
                }
                catch
                {
                    // in case the process hasn't started yet
                    // or has already terminated
                }
            });

            proc.Exited += async(_, @event) =>
            {
                var code   = proc.ExitCode;
                var stdErr = await proc.StandardError.ReadToEndAsync().ConfigureAwait(false);

                var result = new CommandResult(code, standardOutputBuilder.ToString(), stdErr);
                if (code != 0)
                {
                    var ex = CommandException.CreateFromResult(result);
                    tcs.TrySetException(ex);
                }
                else
                {
                    tcs.TrySetResult(result);
                }
            };

            proc.Start();
            proc.BeginOutputReadLine();
            return(await tcs.Task.ConfigureAwait(false));
        }