Skip to content

Commit

Permalink
Merge branch 'main' into import-postman-collection
Browse files Browse the repository at this point in the history
  • Loading branch information
frankkilcommins authored Jan 31, 2024
2 parents 417437a + 8f2a0bb commit cc4e661
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 52 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ Simple utility CLI for importing data into SwaggerHub Explore.
You will need the following:
- .NET 7.0 (or above). Follow instructions for [Windows](https://learn.microsoft.com/en-us/dotnet/core/install/windows?tabs=net70), [Linux](https://learn.microsoft.com/en-us/dotnet/core/install/linux), or [MacOS](https://learn.microsoft.com/en-us/dotnet/core/install/macos).
- A SwaggerHub Explore account, register at https://try.smartbear.com/swaggerhub-explore (if required).
- If you want to import data from the legacy Swagger Inspector tool, the you'll also need a Swagger Inspector account, register by clicking the `Sign Up` button at https://inspector.swagger.io/builder (if required).

### Install the CLI

Expand All @@ -43,9 +42,7 @@ Download and install the CLI tool from Nuget: https://www.nuget.org/packages/Exp

### Session Cookies for CLI command

You will need to obtain certain cookies from an active session in SwaggerHub Explore to invoke the `CLI` commands. For the `import-inspector-collections` CLI command, you will also need to obtain a cookie from an active Swagger Inspector session.

From Swagger Inspector, navigate to your browser development tools, locate the application cookies and extract the `inspector-token` cookie.
You will need to obtain certain cookies from an active session in SwaggerHub Explore to invoke the `CLI` commands.

From SwaggerHub Explore, navigate to your browser development tools, locate the application cookies and extract the `SESSION` and `XSRF-TOKEN` cookies.

Expand Down Expand Up @@ -132,6 +129,8 @@ From SwaggerHub Explore, navigate to your browser development tools, locate the
> `-fp`, `--file-path` <file-path> (REQUIRED) The path to the file used for importing data
> `-n`, `--names` <names> A comma-separated list of space names to import
> `-v`, `--verbose` Include verbose output during processing
> `-?`, `-h`, `--help` Show help and usage information
Expand Down
84 changes: 36 additions & 48 deletions src/Explore.Cli/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.Net;
using System.Net.Http.Json;
using System.Text;
Expand All @@ -10,6 +10,7 @@ internal class Program
{
public static async Task<int> Main(string[] args)
{
import-postman-collection

Check failure on line 13 in src/Explore.Cli/Program.cs

View workflow job for this annotation

GitHub Actions / build-test-package

; expected

Check failure on line 13 in src/Explore.Cli/Program.cs

View workflow job for this annotation

GitHub Actions / build-test-package

; expected
var rootCommand = new RootCommand
{
Name = "Explore.CLI",
Expand All @@ -28,7 +29,7 @@ public static async Task<int> Main(string[] args)
var exportFileName = new Option<string>(name: "--export-name", description: "The name of the file to export") { IsRequired = false };
exportFileName.AddAlias("-en");

var names = new Option<string>(name: "--names", description: "The names of the spaces to export") { IsRequired = false };
var names = new Option<string>(name: "--names", description: "The names of the spaces to export or import") { IsRequired = false };
names.AddAlias("-n");

var verbose = new Option<bool?>(name: "--verbose", description: "Include verbose output during processing") { IsRequired = false };
Expand All @@ -41,12 +42,12 @@ public static async Task<int> Main(string[] args)
exportSpacesCommand.SetHandler(async (ec, fp, en, n, v) =>
{ await ExportSpaces(ec, fp, en, n, v); }, exploreCookie, exportFilePath, exportFileName, names, verbose);

var importSpacesCommand = new Command("import-spaces") { exploreCookie, importFilePath, verbose };
var importSpacesCommand = new Command("import-spaces") { exploreCookie, importFilePath, names, verbose };
importSpacesCommand.Description = "Import SwaggerHub Explore spaces from a file";
rootCommand.Add(importSpacesCommand);

importSpacesCommand.SetHandler(async (ec, fp, v) =>
{ await ImportSpaces(ec, fp, v); }, exploreCookie, importFilePath, verbose);
importSpacesCommand.SetHandler(async (ec, fp, v, n) =>
{ await ImportSpaces(ec, fp, v, n); }, exploreCookie, importFilePath, names, verbose);

var importPostmanCollectionCommand = new Command("import-postman-collection") { exploreCookie, importFilePath, verbose };
importPostmanCollectionCommand.Description = "Import a Postman collection into SwaggerHub Explore";
Expand Down Expand Up @@ -328,6 +329,7 @@ internal static async Task ExportSpaces(string exploreCookie, string filePath, s
{
if (namesList?.Count > 0 && space.Name != null && !namesList.Contains(space.Name))
{
AnsiConsole.MarkupLine($"[orange3]'Skipped {space.Name}': Name not found in list of names to export[/]");
continue;
}

Expand Down Expand Up @@ -409,22 +411,25 @@ internal static async Task ExportSpaces(string exploreCookie, string filePath, s
}
}

if (spacesToExport.Count == 0)
{
AnsiConsole.MarkupLine($"[orange3]No spaces matched the provided names[/]");
return;
}

// construct the export object
var export = new ExportSpaces()
{
Info = new Info() { ExportedAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") },
ExploreSpaces = spacesToExport
};


// export the file
string exploreSpacesJson = JsonSerializer.Serialize(export);
try
{
using (StreamWriter streamWriter = new StreamWriter(filePath))
{
streamWriter.Write(exploreSpacesJson);
}
using StreamWriter streamWriter = new StreamWriter(filePath);
streamWriter.Write(exploreSpacesJson);
}
catch (UnauthorizedAccessException)
{
Expand All @@ -441,23 +446,21 @@ internal static async Task ExportSpaces(string exploreCookie, string filePath, s
}
}

internal static async Task ImportSpaces(string exploreCookie, string filePath, bool? verboseOutput)
internal static async Task ImportSpaces(string exploreCookie, string filePath, string names, bool? verboseOutput)
{
//check file existence and read permissions
try
{
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using FileStream fs = new(filePath, FileMode.Open, FileAccess.Read);
//let's verify it's a JSON file now
if (!UtilityHelper.IsJsonFile(filePath))
{
//let's verify it's a JSON file now
if (!UtilityHelper.IsJsonFile(filePath))
{
AnsiConsole.MarkupLine($"[red]The file provided is not a JSON file. Please review.[/]");
return;
}

// You can read from the file if this point is reached
AnsiConsole.MarkupLine($"processing ...");
AnsiConsole.MarkupLine($"[red]The file provided is not a JSON file. Please review.[/]");
return;
}

// You can read from the file if this point is reached
AnsiConsole.MarkupLine($"processing ...");
}
catch (UnauthorizedAccessException)
{
Expand All @@ -475,6 +478,9 @@ internal static async Task ImportSpaces(string exploreCookie, string filePath, b
return;
}

var namesList = names?.Split(',')
.Select(name => name.Trim())
.ToList();

//Read and serialize
try
Expand All @@ -485,10 +491,9 @@ internal static async Task ImportSpaces(string exploreCookie, string filePath, b
//validate json against known (high level) schema
var validationResult = await UtilityHelper.ValidateSchema(json, "explore");


if (!validationResult.isValid)
{
Console.WriteLine($"The provide json does not conform to the expected schema. Errors: {validationResult.Message}");
Console.WriteLine($"The provided JSON does not conform to the expected schema. Errors: {validationResult.Message}");
return;
}

Expand All @@ -505,13 +510,19 @@ internal static async Task ImportSpaces(string exploreCookie, string filePath, b
{
foreach (var exportedSpace in exportedSpaces.ExploreSpaces)
{
// check if the space name is in the list of names to import
if (namesList?.Count > 0 && exportedSpace.Name != null && !namesList.Contains(exportedSpace.Name))
{
AnsiConsole.MarkupLine($"[orange3]'Skipped {exportedSpace.Name}': Name not found in list of names to import[/]");
continue;
}

var spaceExists = await CheckSpaceExists(exploreCookie, exportedSpace.Id?.ToString(), verboseOutput);

var resultTable = new Table() { Title = new TableTitle(text: $"PROCESSING [green]{exportedSpace.Name}[/]"), Width = 100, UseSafeBorder = true };
resultTable.AddColumn("Result");
resultTable.AddColumn(new TableColumn("Details").Centered());


var importedSpace = await UpsertSpace(exploreCookie, spaceExists, exportedSpace.Name, exportedSpace.Id.ToString());

if (!string.IsNullOrEmpty(importedSpace.Name))
Expand Down Expand Up @@ -547,7 +558,6 @@ internal static async Task ImportSpaces(string exploreCookie, string filePath, b
}
}
}

}
}
else
Expand All @@ -569,7 +579,6 @@ internal static async Task ImportSpaces(string exploreCookie, string filePath, b
{
AnsiConsole.Write(resultTable);
}

}
}

Expand All @@ -587,10 +596,8 @@ internal static async Task ImportSpaces(string exploreCookie, string filePath, b
}
}


private static async Task<bool> CheckSpaceExists(string exploreCookie, string? id, bool? verboseOutput)
{

if (string.IsNullOrEmpty(id))
{
return false;
Expand All @@ -617,7 +624,6 @@ private static async Task<bool> CheckSpaceExists(string exploreCookie, string? i
return true;
}


if (verboseOutput != null && verboseOutput == true)
{
AnsiConsole.MarkupLine($"[orange3]StatusCode {spacesResponse.StatusCode} returned from the GetSpaceById API. New space will be created[/]");
Expand All @@ -628,7 +634,6 @@ private static async Task<bool> CheckSpaceExists(string exploreCookie, string? i

private static async Task<bool> CheckApiExists(string exploreCookie, string spaceId, string? id, bool? verboseOutput)
{

if (string.IsNullOrEmpty(id))
{
return false;
Expand Down Expand Up @@ -659,7 +664,6 @@ private static async Task<bool> CheckApiExists(string exploreCookie, string spac

private static async Task<bool> CheckConnectionExists(string exploreCookie, string spaceId, string apiId, string? id, bool? verboseOutput)
{

if (string.IsNullOrEmpty(id))
{
return false;
Expand Down Expand Up @@ -688,7 +692,6 @@ private static async Task<bool> CheckConnectionExists(string exploreCookie, stri
return false;
}


private static async Task<SpaceResponse> UpsertSpace(string exploreCookie, bool spaceExists, string? name, string? id)
{
var httpClient = new HttpClient
Expand Down Expand Up @@ -721,7 +724,6 @@ private static async Task<SpaceResponse> UpsertSpace(string exploreCookie, bool
// swallow 409 as server is being overly strict
return new SpaceResponse() { Id = Guid.Parse(id), Name = name };
}

}

if (spacesResponse.IsSuccessStatusCode)
Expand All @@ -741,8 +743,6 @@ private static async Task<SpaceResponse> UpsertSpace(string exploreCookie, bool
return new SpaceResponse();
}



private static async Task<ApiResponse> UpsertApi(string exploreCookie, bool spaceExists, string spaceId, string? id, string? name, string? type, bool? verboseOutput)
{
var httpClient = new HttpClient
Expand Down Expand Up @@ -807,7 +807,6 @@ private static async Task<bool> UpsertConnection(string exploreCookie, bool spac
httpClient.DefaultRequestHeaders.Add("Cookie", exploreCookie);
httpClient.DefaultRequestHeaders.Add("X-Xsrf-Token", $"{UtilityHelper.ExtractXSRFTokenFromCookie(exploreCookie)}");


var connectionContent = new StringContent(JsonSerializer.Serialize(MappingHelper.MassageConnectionExportForImport(connection)), Encoding.UTF8, "application/json");

HttpResponseMessage? connectionResponse;
Expand Down Expand Up @@ -838,18 +837,7 @@ private static async Task<bool> UpsertConnection(string exploreCookie, bool spac
var message = await connectionResponse.Content.ReadAsStringAsync();
AnsiConsole.WriteLine($"error: {message}");
}

}

return false;
}
}









}

0 comments on commit cc4e661

Please sign in to comment.