Skip to content

Commit

Permalink
Adds the preset get command. Closes #438 (#441)
Browse files Browse the repository at this point in the history
  • Loading branch information
waldekmastykarz authored Dec 20, 2023
1 parent 129fdde commit 393eaa1
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 1 deletion.
242 changes: 242 additions & 0 deletions dev-proxy/PresetGetCommandHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.DevProxy.Abstractions;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Microsoft.DevProxy;

class ProxyPresetInfo
{
public IList<string> ConfigFiles { get; set; } = new List<string>();
public IList<string> MockFiles { get; set; } = new List<string>();
}

class GitHubTreeResponse
{
[JsonPropertyName("tree")]
public GitHubTreeItem[] Tree { get; set; } = Array.Empty<GitHubTreeItem>();
[JsonPropertyName("truncated")]
public bool Truncated { get; set; }
}

class GitHubTreeItem
{
[JsonPropertyName("path")]
public string Path { get; set; } = string.Empty;
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
}

public static class PresetGetCommandHandler
{
public static async Task DownloadPreset(string presetId, ILogger logger)
{
try
{
var appFolder = ProxyUtils.AppFolder;
if (string.IsNullOrEmpty(appFolder) || !Directory.Exists(appFolder))
{
logger.LogError("App folder not found");
return;
}

var presetsFolderPath = Path.Combine(appFolder, "presets");
logger.LogDebug($"Checking if presets folder {presetsFolderPath} exists...");
if (!Directory.Exists(presetsFolderPath))
{
logger.LogDebug("Presets folder not found, creating it...");
Directory.CreateDirectory(presetsFolderPath);
logger.LogDebug("Presets folder created");
}

logger.LogDebug($"Getting target folder path for preset {presetId}...");
var targetFolderPath = GetTargetFolderPath(appFolder, presetId);
logger.LogDebug($"Creating target folder {targetFolderPath}...");
Directory.CreateDirectory(targetFolderPath);

logger.LogInfo($"Downloading preset {presetId}...");

var sampleFiles = await GetFilesToDownload(presetId, logger);
foreach (var sampleFile in sampleFiles)
{
await DownloadFile(sampleFile, targetFolderPath, presetId, logger);
}

logger.LogInfo($"Preset saved in {targetFolderPath}");
var presetInfo = GetPresetInfo(targetFolderPath, logger);
if (!presetInfo.ConfigFiles.Any() && !presetInfo.MockFiles.Any())
{
return;
}

logger.LogInfo("");
if (presetInfo.ConfigFiles.Any())
{
logger.LogInfo("To start Dev Proxy with the preset, run:");
foreach (var configFile in presetInfo.ConfigFiles)
{
logger.LogInfo($" devproxy --config-file \"{configFile.Replace(appFolder, "~appFolder")}\"");
}
}
else
{
logger.LogInfo("To start Dev Proxy with the mock file, enable the MockResponsePlugin or GraphMockResponsePlugin and run:");
foreach (var mockFile in presetInfo.MockFiles)
{
logger.LogInfo($" devproxy --mock-file \"{mockFile.Replace(appFolder, "~appFolder")}\"");
}
}
}
catch (Exception ex)
{
logger.LogError(ex.Message);
}
}

/// <summary>
/// Returns the list of files that can be used as entry points for the preset
/// </summary>
/// <remarks>
/// A sample in the gallery can have multiple entry points. It can
/// contain multiple config files or no config files and a multiple
/// mock files. This method returns the list of files that Dev Proxy
/// can use as entry points.
/// If there's one or more config files, it'll return an array of
/// these file names. If there are no proxy configs, it'll return
/// an array of all the mock files. If there are no mocks, it'll return
/// an empty array indicating that there's no entry point.
/// </remarks>
/// <param name="presetFolder">Full path to the folder with preset files</param>
/// <returns>Array of files that can be used to start proxy with</returns>
private static ProxyPresetInfo GetPresetInfo(string presetFolder, ILogger logger)
{
var presetInfo = new ProxyPresetInfo();

logger.LogDebug($"Getting list of JSON files in {presetFolder}...");
var jsonFiles = Directory.GetFiles(presetFolder, "*.json");
if (!jsonFiles.Any())
{
logger.LogDebug("No JSON files found");
return presetInfo;
}

foreach (var jsonFile in jsonFiles)
{
logger.LogDebug($"Reading file {jsonFile}...");

var fileContents = File.ReadAllText(jsonFile);
if (fileContents.Contains("\"plugins\":"))
{
logger.LogDebug($"File {jsonFile} contains proxy config");
presetInfo.ConfigFiles.Add(jsonFile);
continue;
}

if (fileContents.Contains("\"responses\":"))
{
logger.LogDebug($"File {jsonFile} contains mock data");
presetInfo.MockFiles.Add(jsonFile);
continue;
}

logger.LogDebug($"File {jsonFile} is not a proxy config or mock data");
}

if (presetInfo.ConfigFiles.Any())
{
logger.LogDebug($"Found {presetInfo.ConfigFiles.Count} proxy config files. Clearing mocks...");
presetInfo.MockFiles.Clear();
}

return presetInfo;
}

private static string GetTargetFolderPath(string appFolder, string presetId)
{
var baseFolder = Path.Combine(appFolder, "presets", presetId);
var newFolder = baseFolder;
var i = 1;
while (Directory.Exists(newFolder))
{
newFolder = baseFolder + i++;
}

return newFolder;
}

private static async Task<string[]> GetFilesToDownload(string sampleFolderName, ILogger logger)
{
logger.LogDebug($"Getting list of files in Dev Proxy samples repo...");
var url = $"https://api.github.com/repos/pnp/proxy-samples/git/trees/main?recursive=1";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("dev-proxy", ProxyUtils.ProductVersion));
var response = await client.GetAsync(url);

if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var tree = JsonSerializer.Deserialize<GitHubTreeResponse>(content);
if (tree is null)
{
throw new Exception("Failed to get list of files from GitHub");
}

var samplePath = $"samples/{sampleFolderName}";

var filesToDownload = tree.Tree
.Where(f => f.Path.StartsWith(samplePath, StringComparison.OrdinalIgnoreCase) && f.Type == "blob")
.Select(f => f.Path)
.ToArray();

foreach (var file in filesToDownload)
{
logger.LogDebug($"Found file {file}");
}

return filesToDownload;
}
else
{
throw new Exception($"Failed to get list of files from GitHub. Status code: {response.StatusCode}");
}
}
}

private static async Task DownloadFile(string filePath, string targetFolderPath, string presetId, ILogger logger)
{
var url = $"https://raw.githubusercontent.com/pnp/proxy-samples/main/{filePath.Replace("#", "%23")}";
logger.LogDebug($"Downloading file {filePath}...");

using (var client = new HttpClient())
{
var response = await client.GetAsync(url);

if (response.IsSuccessStatusCode)
{
var contentStream = await response.Content.ReadAsStreamAsync();
var filePathInsideSample = Path.GetRelativePath($"samples/{presetId}", filePath);
var directoryNameInsideSample = Path.GetDirectoryName(filePathInsideSample);
if (directoryNameInsideSample is not null)
{
Directory.CreateDirectory(Path.Combine(targetFolderPath, directoryNameInsideSample));
}
var localFilePath = Path.Combine(targetFolderPath, filePathInsideSample);

using (var fileStream = new FileStream(localFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
await contentStream.CopyToAsync(fileStream);
}

logger.LogDebug($"File downloaded successfully to {localFilePath}");
}
else
{
throw new Exception($"Failed to download file {url}. Status code: {response.StatusCode}");
}
}
}
}
13 changes: 12 additions & 1 deletion dev-proxy/ProxyHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.DevProxy.Abstractions;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Net;

namespace Microsoft.DevProxy;
Expand Down Expand Up @@ -209,9 +210,19 @@ public RootCommand GetRootCommand(ILogger logger)
};
command.Add(msGraphDbCommand);

var presetCommand = new Command("preset", "Manage Dev Proxy presets");

var presetGetCommand = new Command("get", "Download the specified preset from the Sample Solution Gallery");
var presetIdArgument = new Argument<string>("preset-id", "The ID of the preset to download");
presetGetCommand.AddArgument(presetIdArgument);
presetGetCommand.SetHandler(async presetId => await PresetGetCommandHandler.DownloadPreset(presetId, logger), presetIdArgument);
presetCommand.Add(presetGetCommand);

command.Add(presetCommand);

return command;
}

public ProxyCommandHandler GetCommandHandler(PluginEvents pluginEvents, ISet<UrlToWatch> urlsToWatch, ILogger logger) => new ProxyCommandHandler(_portOption, _ipAddressOption, _logLevelOption, _recordOption, _watchPidsOption, _watchProcessNamesOption, _rateOption, _noFirstRunOption, pluginEvents, urlsToWatch, logger);
public ProxyCommandHandler GetCommandHandler(PluginEvents pluginEvents, ISet<UrlToWatch> urlsToWatch, ILogger logger) => new ProxyCommandHandler(_portOption, _ipAddressOption, _logLevelOption!, _recordOption, _watchPidsOption, _watchProcessNamesOption, _rateOption, _noFirstRunOption, pluginEvents, urlsToWatch, logger);
}

0 comments on commit 393eaa1

Please sign in to comment.