Ejemplo n.º 1
0
        /// <inheritdoc/>
        public override void Run(CommandLine commandLine)
        {
            if (commandLine.HasHelpOption)
            {
                Console.WriteLine(usage);
                Program.Exit(0);
            }

            if (commandLine.Arguments.Length < 2)
            {
                Console.WriteLine(usage);
                Program.Exit(1);
            }

            if (Environment.GetEnvironmentVariable("NEON_RUN_ENV") != null)
            {
                Console.Error.WriteLine("*** ERROR: [neon run ...] cannot be executed recursively.");
                Program.Exit(1);
            }

            var commandSplit     = Program.CommandLine.Split();
            var leftCommandLine  = commandSplit.Left.Shift(1);
            var rightCommandLine = commandSplit.Right;

            if (rightCommandLine == null || rightCommandLine.Arguments.Length == 0)
            {
                Console.Error.WriteLine("*** ERROR: Expecting a [--] argument followed by a shell command.");
                Program.Exit(1);
            }

            var orgDirectory = Directory.GetCurrentDirectory();
            var runFolder    = Path.Combine(HiveHelper.GetRunFolder(), Guid.NewGuid().ToString("D"));
            var runEnvPath   = Path.Combine(runFolder, "__runenv.txt");
            var exitCode     = 1;

            try
            {
                // Create the temporary run folder and make it the current directory.

                Directory.CreateDirectory(runFolder);

                // We need to load variables from any files specified on the command line,
                // decrypting them as required.

                var allVars = new Dictionary <string, string>(StringComparer.InvariantCultureIgnoreCase);

                if (leftCommandLine.Arguments.Length > 0)
                {
                    bool   askVaultPass     = leftCommandLine.HasOption("--ask-vault-pass");
                    string tempPasswordPath = null;
                    string passwordName     = null;

                    try
                    {
                        if (askVaultPass)
                        {
                            // Note that [--ask-vault-pass] takes presidence over [--vault-password-file].

                            var password = NeonHelper.ReadConsolePassword("Vault password: "******"D");

                            tempPasswordPath = Path.Combine(passwordsFolder, $"{guid}.tmp");
                            passwordName     = Path.GetFileName(tempPasswordPath);

                            File.WriteAllText(tempPasswordPath, password);
                        }
                        else
                        {
                            passwordName = leftCommandLine.GetOption("--vault-password-file");
                        }

                        if (!string.IsNullOrEmpty(passwordName))
                        {
                            AnsibleCommand.VerifyPassword(passwordName);
                        }

                        // Decrypt the variables files, add the variables to the environment
                        // and also to the [allVars] dictionary which we'll use below to
                        // create the run variables file.

                        foreach (var varFile in leftCommandLine.Arguments)
                        {
                            var varContents = File.ReadAllText(varFile);

                            if (varContents.StartsWith("$ANSIBLE_VAULT;"))
                            {
                                // The variable file is encrypted so we're going recursively invoke
                                // the following command to decrypt it:
                                //
                                //      neon ansible vault view -- --vault-password=NAME VARS-PATH
                                //
                                // This uses the password to decrypt the variables to STDOUT.

                                if (string.IsNullOrEmpty(passwordName))
                                {
                                    Console.Error.WriteLine($"*** ERROR: [{varFile}] is encrypted.  Use [--ask-vault-pass] or [--vault-password-file] to specify the password.");
                                    Program.Exit(1);
                                }

                                var result = Program.ExecuteRecurseCaptureStreams(
                                    new object[]
                                {
                                    "ansible",
                                    "vault",
                                    "--",
                                    "view",
                                    $"--vault-password-file={passwordName}",
                                    varFile
                                });

                                if (result.ExitCode != 0)
                                {
                                    Console.Error.Write(result.AllText);
                                    Program.Exit(result.ExitCode);
                                }

                                varContents = NeonHelper.StripAnsibleWarnings(result.OutputText);
                            }

                            // [varContents] now holds the decrypted variables formatted as YAML.
                            // We're going to parse this and set the appropriate environment
                            // variables.
                            //
                            // Note that we're going to ignore variables with multi-line values.

                            var yaml = new YamlStream();
                            var vars = new List <KeyValuePair <string, string> >();

                            try
                            {
                                yaml.Load(varContents);
                            }
                            catch (Exception e)
                            {
                                throw new HiveException($"Unable to parse YAML from decrypted [{varFile}]: {NeonHelper.ExceptionError(e)}", e);
                            }

                            if (yaml.Documents.FirstOrDefault() != null)
                            {
                                ParseYamlVariables(vars, (YamlMappingNode)yaml.Documents.First().RootNode);
                            }

                            foreach (var variable in vars)
                            {
                                if (variable.Value != null && variable.Value.Contains('\n'))
                                {
                                    continue;   // Ignore variables with multi-line values.
                                }

                                allVars[variable.Key] = variable.Value;
                                Environment.SetEnvironmentVariable(variable.Key, variable.Value);
                            }
                        }
                    }
                    finally
                    {
                        if (tempPasswordPath != null && File.Exists(tempPasswordPath))
                        {
                            File.Delete(tempPasswordPath);  // Don't need this any more.
                        }
                    }
                }

                // We need to generate the NEON_RUN_ENV file defining the environment variables
                // loaded by the command.  This file format is compatible with the Docker
                // [run] command's [--env-file=PATH] option and will be used by nested calls to
                // [neon] to pass these variables through to the tool container as required.

                Environment.SetEnvironmentVariable("NEON_RUN_ENV", runEnvPath);

                using (var runEnvWriter = new StreamWriter(runEnvPath, false, Encoding.UTF8))
                {
                    foreach (var item in allVars)
                    {
                        runEnvWriter.WriteLine($"{item.Key}={item.Value}");
                    }
                }

                // Execute the command in the appropriate shell for the current workstation.

                var sbCommand = new StringBuilder();

                foreach (var arg in rightCommandLine.Items)
                {
                    if (sbCommand.Length > 0)
                    {
                        sbCommand.Append(' ');
                    }

                    if (arg.Contains(' '))
                    {
                        sbCommand.Append("\"" + arg + "\"");
                    }
                    else
                    {
                        sbCommand.Append(arg);
                    }
                }

                exitCode = NeonHelper.ExecuteShell(sbCommand.ToString());
            }
            finally
            {
                // Restore the current directory.

                Directory.SetCurrentDirectory(orgDirectory);

                // Cleanup

                if (Directory.Exists(runFolder))
                {
                    Directory.Delete(runFolder, true);
                }
            }

            Program.Exit(exitCode);
        }