Skip to content

Commit

Permalink
Initial Upload code added.
Browse files Browse the repository at this point in the history
Initial code added for .NET Tool.
TODO: Unit Tests, Integration Tests, MetaData.
  • Loading branch information
janstaelensskyline committed Dec 5, 2023
1 parent fb73112 commit e48518b
Show file tree
Hide file tree
Showing 10 changed files with 567 additions and 52 deletions.
21 changes: 17 additions & 4 deletions CICD.Tools.CatalogUpload.Lib/CICD.Tools.CatalogUpload.Lib.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<AssemblyName>Skyline.DataMiner.CICD.Tools.CatalogUpload.Lib</AssemblyName>
Expand All @@ -16,13 +16,26 @@
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Authors>SkylineCommunications</Authors>
<Company>Skyline Communications</Company>
<Description>Library containing code to upload artifacts to the Skyline DataMiner Catalog (https://catalog.dataminer.services/)</Description>
<Description>
Library containing code to upload artifacts to the Skyline DataMiner Catalog (https://catalog.dataminer.services/).
Entry Point: new CatalogArtifact("PathToArtifact.dmapp", new HttpCatalogService(httpClient, debugLogger)).Upload(CatalogArtifactAccessType.privateCatalog, out output);
</Description>
<RootNamespace>Skyline.DataMiner.CICD.Tools.CatalogUpload.Lib</RootNamespace>
</PropertyGroup>

<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
<None Include="LICENSE.txt" Pack="true" PackagePath="" />
<None Include="nuget\Icon.png" Pack="true" PackagePath="" />
</ItemGroup>


<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Skyline.DataMiner.CICD.FileSystem" Version="1.0.2-ReadAllBytes1" />
<PackageReference Include="Skyline.DataMiner.CICD.Tools.WinEncryptedKeys.Lib" Version="1.0.2" />
</ItemGroup>

</Project>
158 changes: 158 additions & 0 deletions CICD.Tools.CatalogUpload.Lib/CatalogArtifact.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
namespace Skyline.DataMiner.CICD.Tools.CatalogUpload.Lib
{
using System;
using System.Runtime.InteropServices;
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>
/// </summary>
public class CatalogArtifact
{
private readonly ILogger _logger;
private readonly CatalogMetaData metaData;

/// <summary>
/// Creates an instance of <see cref="CatalogArtifact"/>.
/// It searches for an optional dmcatalogkey in the "dmcatalogkey" or "dmcatalogkey_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>
/// <param name="fileSystem">An instance of <see cref="IFileSystem"/> to access the filesystem. e.g. Skyline.DataMiner.CICD.FileSystem.Instance.</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, ICatalogService service, IFileSystem fileSystem, ILogger logger, CatalogMetaData metaData)
{
this.metaData = metaData;
_logger = logger;
Fs = fileSystem;
cancellationTokenSource = new CancellationTokenSource();
catalogService = service;
PathToArtifact = pathToArtifact;
TryFindEnvironmentKey();
}

/// <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.
/// 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>
/// Path to the ".dmapp" or ".dmprotocol" file.
/// </summary>
public string PathToArtifact { get; private set; }

private CancellationTokenSource cancellationTokenSource { get; set; }

private ICatalogService catalogService { get; set; }

private IFileSystem Fs { get; set; }

private string keyFromEnv { get; set; }

/// <summary>
/// Cancels an ongoing upload. Create a new CatalogArtifact to attempt a new upload.
/// </summary>
public void CancelUpload()
{
_logger.LogDebug($"Upload cancellation requested for {PathToArtifact}");
cancellationTokenSource.Cancel();
}

/// <summary>
/// Uploads to the private catalog using the provided dmcatalogkey.
/// </summary>
/// <param name="dmcatalogkey">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)
{
_logger.LogDebug($"Uploading {PathToArtifact}...");

byte[] packageData = Fs.File.ReadAllBytes(PathToArtifact);
var result = await catalogService.ArtifactUploadAsync(packageData, dmcatalogkey, 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.
/// </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()
{
if (String.IsNullOrWhiteSpace(keyFromEnv))
{
throw new InvalidOperationException("Uploading failed, missing token in environment variable dmcatalogkey or dmcatalogkey_encrypted.");
}

_logger.LogDebug($"Attempting upload with Environment Variable as token for artifact: {PathToArtifact}...");
return await UploadAsync(keyFromEnv).ConfigureAwait(false);
}

/// <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>
/// </summary>
private void TryFindEnvironmentKey()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var encryptedKey = WinEncryptedKeys.Lib.Keys.RetrieveKey("dmcatalogkey_encrypted");
if (encryptedKey != null)
{
string keyFromWinEncryptedKeys = encryptedKey.ToString();

if (!String.IsNullOrWhiteSpace(keyFromWinEncryptedKeys))
{
_logger.LogDebug("OK: Found token in Env Variable: 'dmcatalogkey_encrypted' created by WinEncryptedKeys.");
keyFromEnv = keyFromWinEncryptedKeys;
}
}
}

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

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

keyFromEnv = keyFromEnvironment;
}
}
}
}
169 changes: 169 additions & 0 deletions CICD.Tools.CatalogUpload.Lib/CatalogService/CatalogMetaData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
namespace Skyline.DataMiner.CICD.Tools.CatalogUpload.Lib
{
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Xml.Linq;

public class CatalogMetaData
{
public string Branch { get; set; } = "";

public string CommitterMail { get; set; } = "";

public string ContentType { get; set; } = "";

public string Identifier { get; set; } = "";

public bool IsPreRelease { get; set; }

public string Name { get; set; } = "";

public string ReleaseUri { get; set; } = "";

public string Version { get; set; } = "";

public static CatalogMetaData FromArtifact(string pathToArtifact)
{
CatalogMetaData meta;

if (String.IsNullOrWhiteSpace(pathToArtifact))
{
throw new ArgumentNullException(nameof(pathToArtifact));
}

if (pathToArtifact.EndsWith(".dmapp", StringComparison.InvariantCultureIgnoreCase))
{
meta = CatalogMetaData.FromDmapp(pathToArtifact);
}
else if (pathToArtifact.EndsWith(".dmprotocol", StringComparison.InvariantCultureIgnoreCase))
{
meta = CatalogMetaData.FromDmprotocol(pathToArtifact);
}
else
{
throw new InvalidOperationException($"Invalid path to artifact. Expected a path that ends with .dmapp or .dmprotocol but received {pathToArtifact}");
}

return meta;
}

// Used during unit testing to assert data.
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
public override int GetHashCode()
{
return HashCode.Combine(Version, Branch, IsPreRelease, Identifier, Name, ContentType, CommitterMail, ReleaseUri);
}

private static CatalogMetaData FromDmapp(string pathToDmapp)
{
// Open as a ZIP file.
/*AppInfo.xml
*
* <?xml version="1.0" encoding="utf-8"?>
<AppInfo>
<DisplayName>COX Communications CISCO CBR-8 CCAP Platform Collector</DisplayName>
<LastModifiedAt>2023-11-24T14:05:47</LastModifiedAt>
<MinDmaVersion>10.0.9.0-9312</MinDmaVersion>
<Name>COX Communications CISCO CBR-8 CCAP Platform Collector</Name>
<AllowMultipleInstalledVersions>false</AllowMultipleInstalledVersions>
<Version>0.0.0-CU1</Version>
</AppInfo>
*
Description.txt
Bridge Technologies VB Probe Series package version: 0.0.0-CU2
---------------------------------
Package creation time: 2023-11-24 13:38:17
---------------------------------
File Versions:
Visio\skyline_Bridge Technologies VB Probe Series:0.0.0-CU2
*/

string appInfoRaw;

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

using (var stream = foundFile.Open())
{
using (var memoryStream = new StreamReader(stream))
{
appInfoRaw = memoryStream.ReadToEnd();
}
}
}

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

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

string descriptionText;

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

using (var stream = foundFile.Open())
{
using (var memoryStream = new StreamReader(stream))
{
descriptionText = memoryStream.ReadToEnd();
}
}
}

CatalogMetaData meta = new CatalogMetaData();

using(var reader = new StringReader(descriptionText))
{
var line = reader.ReadLine();
var splitLine = line.Split(':');

switch (splitLine[0])
{
case "Protocol Name":
meta.Name = splitLine[1];
break;
case "Protocol Version":
meta.Version = splitLine[1];
break;
default:
break;
}
}

return meta;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Net.Http;

using Microsoft.Extensions.Logging;

namespace Skyline.DataMiner.CICD.Tools.CatalogUpload.Lib.CatalogService
{
/// <summary>
/// Creates instances of <see cref="ICatalogService"/> to communicate with the Skyline DataMiner Catalog (https://catalog.dataminer.services/).
/// </summary>
public static class CatalogServiceFactory
{
/// <summary>
/// Creates instances of <see cref="ICatalogService"/> to communicate with the Skyline DataMiner Catalog (https://catalog.dataminer.services/) using HTTP for communication.
/// </summary>
/// <param name="httpClient">An instance of <see cref="HttpClient"/> used for communication with the catalog.</param>
/// <param name="logger">An instance of <see cref="ILogger"/> for handling debug and error logging.</param>
/// <returns>An instance of <see cref="ICatalogService"/> to communicate with the Skyline DataMiner Catalog (https://catalog.dataminer.services/).</returns>
public static ICatalogService CreateWithHttp(HttpClient httpClient, ILogger logger)
{
return new HttpCatalogService(httpClient, logger);
}
}
}
Loading

0 comments on commit e48518b

Please sign in to comment.