Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use utf8 encoding for reading stdout & stderr when invoking Bicep #23409

Merged
merged 2 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// limitations under the License.
// ----------------------------------------------------------------------------------

using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using Microsoft.Azure.Commands.ResourceManager.Cmdlets.Utilities;
using Microsoft.Azure.Commands.ResourceManager.Common;
using Microsoft.WindowsAzure.Commands.Utilities.Common;
Expand Down Expand Up @@ -41,7 +42,7 @@ public class PublishAzureBicepModuleCmdlet : AzureRMCmdlet

public override void ExecuteCmdlet()
{
BicepUtility.PublishFile(this.TryResolvePath(this.FilePath), this.Target, this.DocumentationUri, this.Force.IsPresent, this.WriteVerbose, this.WriteWarning);
BicepUtility.Create().PublishFile(this.TryResolvePath(this.FilePath), this.Target, this.DocumentationUri, this.Force.IsPresent, this.WriteVerbose, this.WriteWarning);

if (this.PassThru.IsPresent)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ protected string[] GetStaticParameterNames()

protected void BuildAndUseBicepTemplate()
{
TemplateFile = BicepUtility.BuildFile(this.ResolvePath(TemplateFile), this.WriteVerbose, this.WriteWarning);
TemplateFile = BicepUtility.Create().BuildFile(this.ResolvePath(TemplateFile), this.WriteVerbose, this.WriteWarning);
}

private IReadOnlyDictionary<string, object> GetDynamicParametersDictionary()
Expand All @@ -490,8 +490,8 @@ private IReadOnlyDictionary<string, object> GetDynamicParametersDictionary()
protected void BuildAndUseBicepParameters(bool emitWarnings)
{
BicepUtility.OutputCallback nullCallback = null;
var output = BicepUtility.BuildParams(this.ResolvePath(TemplateParameterFile), GetDynamicParametersDictionary(), this.WriteVerbose, emitWarnings ? this.WriteWarning : nullCallback);
bicepparamFileParameters = GetParametersFromJson(output.parametersJson);
var output = BicepUtility.Create().BuildParams(this.ResolvePath(TemplateParameterFile), GetDynamicParametersDictionary(), this.WriteVerbose, emitWarnings ? this.WriteWarning : nullCallback);
bicepparamFileParameters = TemplateUtility.ParseTemplateParameterJson(output.parametersJson);

if (TemplateObject == null &&
string.IsNullOrEmpty(TemplateFile) &&
Expand Down Expand Up @@ -545,13 +545,5 @@ private Hashtable GetCombinedTemplateParameterObject()

return TemplateParameterObject;
}

private IReadOnlyDictionary<string, TemplateParameterFileParameter> GetParametersFromJson(string parametersJson)
{
using (var reader = new StringReader(parametersJson))
{
return TemplateUtility.ParseTemplateParameterJson(reader);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ protected string ResolveBicepFile(string TemplateFile)
{
if (BicepUtility.IsBicepFile(TemplateFile))
{
return BicepUtility.BuildFile(this.ResolvePath(TemplateFile), this.WriteVerbose, this.WriteWarning);
return BicepUtility.Create().BuildFile(this.ResolvePath(TemplateFile), this.WriteVerbose, this.WriteWarning);
}
else
return TemplateFile;
Expand All @@ -71,7 +71,7 @@ protected BicepBuildParamsStdout ResolveBicepParameterFile(string TemplateParame
{
if (BicepUtility.IsBicepparamFile(TemplateParameterFile))
{
return BicepUtility.BuildParams(this.ResolvePath(TemplateParameterFile), new Dictionary<string, object>(), this.WriteVerbose, this.WriteWarning);
return BicepUtility.Create().BuildParams(this.ResolvePath(TemplateParameterFile), new Dictionary<string, object>(), this.WriteVerbose, this.WriteWarning);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ public override void ExecuteCmdlet()

if (BicepUtility.IsBicepFile(TemplateFile))
{
filePath = BicepUtility.BuildFile(this.ResolvePath(TemplateFile), this.WriteVerbose, this.WriteWarning);
filePath = BicepUtility.Create().BuildFile(this.ResolvePath(TemplateFile), this.WriteVerbose, this.WriteWarning);
}

// Note: We set uiFormDefinitionFilePath to null below because we process the UIFormDefinition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public override void ExecuteCmdlet()
}
if (BicepUtility.IsBicepFile(TemplateFile))
{
filePath = BicepUtility.BuildFile(this.ResolvePath(TemplateFile), this.WriteVerbose, this.WriteWarning);
filePath = BicepUtility.Create().BuildFile(this.ResolvePath(TemplateFile), this.WriteVerbose, this.WriteWarning);
}

// Note: We set uiFormDefinitionFilePath to null below because we process the UIFormDefinition
Expand Down
66 changes: 38 additions & 28 deletions src/Resources/ResourceManager/Utilities/BicepUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.Utilities
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.Azure.Commands.Common.Authentication;

public class BicepBuildParamsStdout
{
Expand All @@ -33,22 +34,10 @@ public class BicepBuildParamsStdout
public string templateSpecId { get; set; }
}

internal static class BicepUtility
internal class BicepUtility
{
private static Lazy<string> BicepVersionLazy = new Lazy<string>(() => {
var processInvoker = ProcessInvoker.Create();
if (!processInvoker.CheckExecutableExists(BicepExecutable))
{
return null;
}

var output = processInvoker.Invoke(new ProcessInput { Executable = BicepExecutable, Arguments = "-v" });

var pattern = new Regex("\\d+(\\.\\d+)+");
return pattern.Match(output.Stdout)?.Value;
});

private static string BicepVersion => BicepVersionLazy.Value;
public static BicepUtility Create()
=> new BicepUtility(ProcessInvoker.Create(), FileUtilities.DataStore);

/// <summary>
/// The Bicep executable to use. By default, this'll be resolved from the system PATH.
Expand All @@ -72,15 +61,38 @@ internal static class BicepUtility

public delegate void OutputCallback(string msg);

private readonly IProcessInvoker processInvoker;
private readonly IDataStore dataStore;
private readonly Lazy<string> bicepVersionLazy;

public BicepUtility(IProcessInvoker processInvoker, IDataStore dataStore)
{
this.processInvoker = processInvoker;
this.dataStore = dataStore;
this.bicepVersionLazy = new Lazy<string>(() => {
if (!processInvoker.CheckExecutableExists(BicepExecutable))
{
return null;
}

var output = processInvoker.Invoke(new ProcessInput { Executable = BicepExecutable, Arguments = "-v" });

var pattern = new Regex("\\d+(\\.\\d+)+");
return pattern.Match(output.Stdout)?.Value;
});
}

private string BicepVersion => bicepVersionLazy.Value;

public static bool IsBicepFile(string templateFilePath) =>
".bicep".Equals(Path.GetExtension(templateFilePath), StringComparison.OrdinalIgnoreCase);

public static bool IsBicepparamFile(string parametersFilePath) =>
".bicepparam".Equals(Path.GetExtension(parametersFilePath), StringComparison.OrdinalIgnoreCase);

public static string BuildFile(string bicepTemplateFilePath, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
public string BuildFile(string bicepTemplateFilePath, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
{
if (!FileUtilities.DataStore.FileExists(bicepTemplateFilePath))
if (!dataStore.FileExists(bicepTemplateFilePath))
{
throw new AzPSArgumentException(Properties.Resources.InvalidBicepFilePath, "TemplateFile");
}
Expand All @@ -91,17 +103,17 @@ public static string BuildFile(string bicepTemplateFilePath, OutputCallback writ
RunBicepCommand($"build {GetQuotedFilePath(bicepTemplateFilePath)} --outdir {GetQuotedFilePath(tempDirectory)}", MinimalVersionRequirement, writeVerbose, writeWarning);

string buildResultPath = Path.Combine(tempDirectory, Path.GetFileName(bicepTemplateFilePath)).Replace(".bicep", ".json");
if (!FileUtilities.DataStore.FileExists(buildResultPath))
if (!dataStore.FileExists(buildResultPath))
{
throw new AzPSApplicationException(string.Format(Properties.Resources.BuildBicepFileToJsonFailed, bicepTemplateFilePath));
}

return buildResultPath;
}

public static BicepBuildParamsStdout BuildParams(string bicepParamFilePath, IReadOnlyDictionary<string, object> overrideParams, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
public BicepBuildParamsStdout BuildParams(string bicepParamFilePath, IReadOnlyDictionary<string, object> overrideParams, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
{
if (!FileUtilities.DataStore.FileExists(bicepParamFilePath))
if (!dataStore.FileExists(bicepParamFilePath))
{
throw new AzPSArgumentException(Properties.Resources.InvalidBicepparamFilePath, "TemplateParameterFile");
}
Expand All @@ -124,9 +136,9 @@ public static BicepBuildParamsStdout BuildParams(string bicepParamFilePath, IRea
return JsonConvert.DeserializeObject<BicepBuildParamsStdout>(stdout);
}

public static void PublishFile(string bicepFilePath, string target, string documentationUri = null, bool force = false, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
public void PublishFile(string bicepFilePath, string target, string documentationUri = null, bool force = false, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
{
if (!FileUtilities.DataStore.FileExists(bicepFilePath))
if (!dataStore.FileExists(bicepFilePath))
{
throw new AzPSArgumentException(Properties.Resources.InvalidBicepFilePath, "File");
}
Expand All @@ -148,15 +160,15 @@ public static void PublishFile(string bicepFilePath, string target, string docum
RunBicepCommand(bicepPublishCommand, MinimalVersionRequirementForBicepPublish, writeVerbose, writeWarning);
}

private static void CheckBicepExecutable()
private void CheckBicepExecutable()
{
if (BicepVersion == null)
{
throw new AzPSApplicationException(Properties.Resources.BicepNotFound);
}
}

private static string CheckMinimalVersionRequirement(string minimalVersionRequirement)
private string CheckMinimalVersionRequirement(string minimalVersionRequirement)
{
CheckBicepExecutable();

Expand All @@ -168,14 +180,13 @@ private static string CheckMinimalVersionRequirement(string minimalVersionRequir
return BicepVersion;
}

private static string RunBicepCommandWithStdoutCapture(string arguments, string minimalVersionRequirement, Dictionary<string, string> envVars = null, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
private string RunBicepCommandWithStdoutCapture(string arguments, string minimalVersionRequirement, Dictionary<string, string> envVars = null, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
{
string currentBicepVersion = CheckMinimalVersionRequirement(minimalVersionRequirement);
writeVerbose?.Invoke($"Using Bicep v{currentBicepVersion}");

writeVerbose?.Invoke($"Calling Bicep with arguments: {arguments}");

var processInvoker = ProcessInvoker.Create();
var output = processInvoker.Invoke(new ProcessInput { Executable = BicepExecutable, Arguments = arguments, EnvVars = envVars });

if (output.ExitCode != 0)
Expand All @@ -192,14 +203,13 @@ private static string RunBicepCommandWithStdoutCapture(string arguments, string
return output.Stdout;
}

private static void RunBicepCommand(string arguments, string minimalVersionRequirement, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
private void RunBicepCommand(string arguments, string minimalVersionRequirement, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
{
string currentBicepVersion = CheckMinimalVersionRequirement(minimalVersionRequirement);
writeVerbose?.Invoke($"Using Bicep v{currentBicepVersion}");

writeVerbose?.Invoke($"Calling Bicep with arguments: {arguments}");

var processInvoker = ProcessInvoker.Create();
var output = processInvoker.Invoke(new ProcessInput { Executable = BicepExecutable, Arguments = arguments });

writeVerbose?.Invoke(output.Stdout);
Expand Down
3 changes: 3 additions & 0 deletions src/Resources/ResourceManager/Utilities/ProcessInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.Utilities
using System.Collections.Generic;
using System.Diagnostics;
using System.Management.Automation;
using System.Text;

public class ProcessOutput
{
Expand Down Expand Up @@ -84,6 +85,8 @@ public ProcessOutput Invoke(ProcessInput input)
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
CreateNoWindow = true,
}
};
Expand Down
8 changes: 8 additions & 0 deletions src/Resources/ResourceManager/Utilities/TemplateUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ public static Dictionary<string, TemplateParameterFileParameter> ParseTemplatePa
}
}

public static Dictionary<string, TemplateParameterFileParameter> ParseTemplateParameterJson(string json)
{
using (var reader = new StringReader(json))
{
return TemplateUtility.ParseTemplateParameterJson(reader);
}
}

public static Dictionary<string, TemplateParameterFileParameter> ParseTemplateParameterJson(TextReader reader)
{
// Read once to avoid having to rewind the stream
Expand Down
Loading