Skip to content

Commit

Permalink
Code Cleanup.
Browse files Browse the repository at this point in the history
Fixes
Added Unit Tests.
  • Loading branch information
janstaelensskyline committed Dec 7, 2023
1 parent a932b8e commit 088d094
Show file tree
Hide file tree
Showing 20 changed files with 819 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<AssemblyName>Skyline.DataMiner.CICD.Tools.CatalogUpload.Lib</AssemblyName>
<PackageVersion>1.0.1</PackageVersion>
<Version>1.0.1</Version>
<PackageVersion>1.0.1-alpaha1</PackageVersion>
<Version>1.0.1-alpaha1</Version>
<PackageTags>Skyline;DataMiner</PackageTags>
<PackageProjectUrl>https://skyline.be</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
Expand Down
64 changes: 35 additions & 29 deletions CICD.Tools.CatalogUpload.Lib/CatalogArtifact.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,17 @@
using System.Threading;
using System.Threading.Tasks;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

using Newtonsoft.Json;

using Skyline.DataMiner.CICD.FileSystem;
using Skyline.DataMiner.CICD.Tools.CatalogUpload.Lib.HttpArtifactUploadModels;

/// <summary>
/// Allows Uploading an artifact to the Catalog using one of the below in order of priority:
/// <para>- provided key in upload argument (unix/win)</para>
/// <para>- key stored as an Environment Variable called "dmcatalogkey". (unix/win)</para>
/// <para>- key configured using Skyline.DataMiner.CICD.Tools.WinEncryptedKeys called "dmcatalogkey_encrypted" (windows only)</para>
/// <para>- key stored as an Environment Variable called "dmcatalogtoken". (unix/win)</para>
/// <para>- key configured using Skyline.DataMiner.CICD.Tools.WinEncryptedKeys called "dmcatalogtoken_encrypted" (windows only)</para>
/// </summary>
public class CatalogArtifact
{
Expand All @@ -26,7 +24,7 @@ public class CatalogArtifact

/// <summary>
/// Creates an instance of <see cref="CatalogArtifact"/>.
/// It searches for an optional dmcatalogkey in the "dmcatalogkey" or "dmcatalogkey_encrypted" Environment Variable.
/// It searches for an optional dmCatalogToken in the "dmcatalogtoken" or "dmcatalogtoken_encrypted" Environment Variable.
/// </summary>
/// <param name="pathToArtifact">Path to the ".dmapp" or ".dmprotocol" file.</param>
/// <param name="service">An instance of <see cref="ICatalogService"/> used for communication.</param>
Expand All @@ -46,15 +44,14 @@ public CatalogArtifact(string pathToArtifact, ICatalogService service, IFileSyst

/// <summary>
/// Creates an instance of <see cref="CatalogArtifact"/> using a default HttpCatalogService with a new HttpClient for communication.
/// It searches for an optional dmcatalogkey in the "dmcatalogkey" or "dmcatalogkey_encrypted" Environment Variable for authentication.
/// It searches for an optional dmCatalogToken in the "dmcatalogtoken" or "dmcatalogtoken_encrypted" Environment Variable for authentication.
/// WARNING: when wishing to upload several Artifacts it's recommended to use the CatalogArtifact(string pathToArtifact, ICatalogService service, IFileSystem fileSystem, ILogger logger).
/// </summary>
/// <param name="pathToArtifact">Path to the ".dmapp" or ".dmprotocol" file.</param>
/// <param name="logger">An instance of <see cref="ILogger"/> that will hold error, debug and other information.</param>
/// <param name="metaData">Contains package metadata.</param>
public CatalogArtifact(string pathToArtifact, ILogger logger, CatalogMetaData metaData) : this(pathToArtifact, new HttpCatalogService(new System.Net.Http.HttpClient(), logger), FileSystem.Instance, logger, metaData)
{

}

/// <summary>
Expand All @@ -80,33 +77,33 @@ public void CancelUpload()
}

/// <summary>
/// Uploads to the private catalog using the provided dmcatalogkey.
/// Uploads to the private catalog using the provided dmCatalogToken.
/// </summary>
/// <param name="dmcatalogkey">A provided token for the agent or organization as defined in https://admin.dataminer.services/.</param>
/// <param name="dmCatalogToken">A provided token for the agent or organization as defined in https://admin.dataminer.services/.</param>
/// <returns>If the upload was successful or not.</returns>
public async Task<ArtifactModel> UploadAsync(string dmcatalogkey)
public async Task<ArtifactUploadResult> UploadAsync(string dmCatalogToken)
{
_logger.LogDebug($"Uploading {PathToArtifact}...");

byte[] packageData = Fs.File.ReadAllBytes(PathToArtifact);
var result = await catalogService.ArtifactUploadAsync(packageData, dmcatalogkey, metaData, cancellationTokenSource.Token).ConfigureAwait(false);
var result = await catalogService.ArtifactUploadAsync(packageData, dmCatalogToken, metaData, cancellationTokenSource.Token).ConfigureAwait(false);
_logger.LogDebug($"Finished Uploading {PathToArtifact}");

_logger.LogInformation(JsonConvert.SerializeObject(result));
return result;
}

/// <summary>
/// Uploads to the private catalog using the dmcatalogkey or dmcatalogkey_encrypted environment variable as the token.
/// Uploads to the private catalog using the dmcatalogtoken or dmcatalogtoken environment variable as the token.
/// </summary>
/// <returns>If the upload was successful or not.</returns>
/// <exception cref="InvalidOperationException">Uploading failed.</exception>
/// <exception cref="UnauthorizedAccessException">Uploading failed due to invalid Token.</exception>
public async Task<ArtifactModel> UploadAsync()
public async Task<ArtifactUploadResult> UploadAsync()
{
if (String.IsNullOrWhiteSpace(keyFromEnv))
{
throw new InvalidOperationException("Uploading failed, missing token in environment variable dmcatalogkey or dmcatalogkey_encrypted.");
throw new InvalidOperationException("Uploading failed, missing token in environment variable dmcatalogtoken or dmcatalogtoken_encrypted.");
}

_logger.LogDebug($"Attempting upload with Environment Variable as token for artifact: {PathToArtifact}...");
Expand All @@ -115,40 +112,49 @@ public async Task<ArtifactModel> UploadAsync()

/// <summary>
/// Attempts to find the necessary API key in Environment Variables. In order of priority:
/// <para>- key stored as an Environment Variable called "dmcatalogkey". (unix/win)</para>
/// <para>- key configured using Skyline.DataMiner.CICD.Tools.WinEncryptedKeys called "dmcatalogkey_encrypted" (windows only)</para>
/// <para>- key stored as an Environment Variable called "dmcatalogtoken". (unix/win)</para>
/// <para>- key configured using Skyline.DataMiner.CICD.Tools.WinEncryptedKeys called "dmcatalogtoken_encrypted" (windows only)</para>
/// </summary>
private void TryFindEnvironmentKey()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var encryptedKey = WinEncryptedKeys.Lib.Keys.RetrieveKey("dmcatalogkey_encrypted");
if (encryptedKey != null)
try
{
string keyFromWinEncryptedKeys = encryptedKey.ToString();

if (!String.IsNullOrWhiteSpace(keyFromWinEncryptedKeys))
var encryptedKey = WinEncryptedKeys.Lib.Keys.RetrieveKey("dmcatalogtoken_encrypted");
if (encryptedKey != null)
{
_logger.LogDebug("OK: Found token in Env Variable: 'dmcatalogkey_encrypted' created by WinEncryptedKeys.");
keyFromEnv = keyFromWinEncryptedKeys;
string keyFromWinEncryptedKeys = new System.Net.NetworkCredential(string.Empty, encryptedKey).Password;

if (!String.IsNullOrWhiteSpace(keyFromWinEncryptedKeys))
{
_logger.LogDebug("OK: Found token in Env Variable: 'dmcatalogtoken_encrypted' created by WinEncryptedKeys.");
keyFromEnv = keyFromWinEncryptedKeys;
}
}
}
catch (InvalidOperationException)
{
// Gobble up, no key means we try the next thing.
}
}

var config = new ConfigurationBuilder()
.AddUserSecrets<CatalogArtifact>()
.Build();
string keyFromEnvironment = config["dmcatalogkey"];
//var config = new ConfigurationBuilder()
// .AddUserSecrets<CatalogArtifact>()
// .Build();
//string keyFromEnvironment = config["dmcatalogtoken"];

string keyFromEnvironment = Environment.GetEnvironmentVariable("dmcatalogtoken");

if (!String.IsNullOrWhiteSpace(keyFromEnvironment))
{
if (!String.IsNullOrWhiteSpace(keyFromEnv))
{
_logger.LogDebug("OK: Overriding 'dmcatalogkey_encrypted' with found token in Env Variable: 'dmcatalogkey'.");
_logger.LogDebug("OK: Overriding 'dmcatalogtoken_encrypted' with found token in Env Variable: 'dmcatalogtoken'.");
}
else
{
_logger.LogDebug("OK: Found token in Env Variable: 'dmcatalogkey'.");
_logger.LogDebug("OK: Found token in Env Variable: 'dmcatalogtoken'.");
}

keyFromEnv = keyFromEnvironment;
Expand Down
136 changes: 117 additions & 19 deletions CICD.Tools.CatalogUpload.Lib/CatalogService/CatalogMetaData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,58 @@
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;

/// <summary>
/// Represents all metadata for a package.
/// </summary>
public class CatalogMetaData
{
public string Branch { get; set; } = "";
private bool artifactHadBuildNumber;

public string CommitterMail { get; set; } = "";
/// <summary>
/// The Branch/Range/Category this version belongs to. Defaults to "main"
/// </summary>
public string Branch { get; set; } = "main";

public string ContentType { get; set; } = "";
/// <summary>
/// The e-mail address of the Author, often the committer of a GIT Tag on the sourcecode making the package.
/// </summary>
public string CommitterMail { get; set; }

public string Identifier { get; set; } = "";
/// <summary>
/// The type of content, as understood by the ArtifactUpload.
/// </summary>
public string ContentType { get; set; }

public bool IsPreRelease { get; set; }
/// <summary>
/// An global, readable, unique identifier for this package. This is often the URI to the sourcecode.
/// </summary>
public string Identifier { get; set; }

public string Name { get; set; } = "";
/// <summary>
/// The Name of the Package
/// </summary>
public string Name { get; set; }

public string ReleaseUri { get; set; } = "";
/// <summary>
/// A URI leading to the release notes of this package version.
/// </summary>
public string ReleaseUri { get; set; }

public string Version { get; set; } = "";
/// <summary>
/// The version of the package.
/// </summary>
public string Version { get; set; }

/// <summary>
/// Creates a partial CataLogMetaData using any information it can from the artifact itself. Check the items for null and complete.
/// </summary>
/// <param name="pathToArtifact">Path to the artifact.</param>
/// <returns>An instance of <see cref="CatalogMetaData"/>.></returns>
/// <exception cref="ArgumentNullException">Provided path should not be null</exception>
/// <exception cref="InvalidOperationException">Expected data was not present in the Artifact.</exception>
public static CatalogMetaData FromArtifact(string pathToArtifact)
{
CatalogMetaData meta;
Expand All @@ -49,24 +81,57 @@ public static CatalogMetaData FromArtifact(string pathToArtifact)
return meta;
}

// Used during unit testing to assert data.
public override bool Equals(object? obj)
/// <summary>
/// Used during unit testing to assert data.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
return obj is CatalogMetaData data &&
Version == data.Version &&
Branch == data.Branch &&
IsPreRelease == data.IsPreRelease &&
Identifier == data.Identifier &&
Name == data.Name &&
CommitterMail == data.CommitterMail &&
ReleaseUri == data.ReleaseUri &&
ContentType == data.ContentType;
}

// Needed to match with Equals
/// <summary>
/// Needed to match with Equals
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return HashCode.Combine(Version, Branch, IsPreRelease, Identifier, Name, ContentType, CommitterMail, ReleaseUri);
return HashCode.Combine(Version, Branch, Identifier, Name, ContentType, CommitterMail, ReleaseUri);
}

/// <summary>
/// Whether this is a pre-release or a full release. This is automatically decided from the Version and artifact content.
/// </summary>
public bool IsPreRelease()
{
// Might need to check for protocols how to handle this. "_BX" probably added to versions?
// Should we allow a force set/override?

if (!artifactHadBuildNumber)
{
// Not a version from .dmapp --> assume semantic versioning
if (!Regex.IsMatch(Version, "^[0-9]+.[0-9]+.[0-9]+(-CU[0-9]+)?$"))
{
return Version.Contains('-');
}
else
{
// Versioning that dataminer uses with the -CU.
return Version.StartsWith("0.0.0-");
}
}
else
{
return true;
}
}

private static CatalogMetaData FromDmapp(string pathToDmapp)
Expand Down Expand Up @@ -97,10 +162,11 @@ private static CatalogMetaData FromDmapp(string pathToDmapp)
*/

string appInfoRaw;
string contentType;

using (var zipFile = ZipFile.OpenRead(pathToDmapp))
{
var foundFile = zipFile.Entries.FirstOrDefault(x => x.Name.Equals("AppInfo.xml", StringComparison.InvariantCulture));
ZipArchiveEntry foundFile = zipFile.GetEntry("AppInfo.xml");
if (foundFile == null) throw new InvalidOperationException("Could not find AppInfo.xml in the .dmapp.");

using (var stream = foundFile.Open())
Expand All @@ -110,21 +176,50 @@ private static CatalogMetaData FromDmapp(string pathToDmapp)
appInfoRaw = memoryStream.ReadToEnd();
}
}

ContentType contentFromPackagContent = new ContentType(zipFile);
contentType = contentFromPackagContent.Value;
}

CatalogMetaData meta = new CatalogMetaData();
var appInfo = XDocument.Parse(appInfoRaw);
var appInfo = XDocument.Parse(appInfoRaw).Root;
meta.Name = appInfo.Element("DisplayName")?.Value;
meta.Version = appInfo.Element("Version")?.Value;

var buildNumber = appInfo.Element("Build")?.Value;

if (!String.IsNullOrWhiteSpace(buildNumber))
{
meta.artifactHadBuildNumber = true;
// Throw away the CU version. If we have a build number it's a pre-release.
string version = appInfo.Element("Version")?.Value;

if (version != null)
{
if (version.Contains("-CU"))
{
// Throw away the CU version. If we have a build number it's a pre-release.
version = version.Split('-')[0];
}

meta.Version = version + "-B" + buildNumber;
}
}
else
{
meta.artifactHadBuildNumber = false;
meta.Version = appInfo.Element("Version")?.Value;
}

meta.ContentType = contentType;
return meta;
}

private static CatalogMetaData FromDmprotocol(string pathToDmprotocol)
{
// Description.txt
/*
Protocol Name: Microsoft Platform
Protocol Version: 6.0.0.4_B2
Protocol Name: Microsoft Platform
Protocol Version: 6.0.0.4_B2
* */

string descriptionText;
Expand All @@ -145,7 +240,7 @@ private static CatalogMetaData FromDmprotocol(string pathToDmprotocol)

CatalogMetaData meta = new CatalogMetaData();

using(var reader = new StringReader(descriptionText))
using (var reader = new StringReader(descriptionText))
{
var line = reader.ReadLine();
var splitLine = line.Split(':');
Expand All @@ -155,14 +250,17 @@ private static CatalogMetaData FromDmprotocol(string pathToDmprotocol)
case "Protocol Name":
meta.Name = splitLine[1];
break;

case "Protocol Version":
meta.Version = splitLine[1];
break;

default:
break;
}
}

meta.ContentType = "protocol";
return meta;
}
}
Expand Down
Loading

0 comments on commit 088d094

Please sign in to comment.