Skip to content

Commit bc9d266

Browse files
Moved CatalogUpload logic to happen on doing a Publish.
TODO: fix catalog API returning Error it could not parse the manifest.yml
1 parent 278dcf3 commit bc9d266

8 files changed

+391
-1
lines changed
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Skyline.DataMiner.Sdk.CatalogService
2+
{
3+
using Newtonsoft.Json;
4+
5+
/// <summary>
6+
/// Artifact information returned from uploading an artifact to the catalog.
7+
/// </summary>
8+
public class ArtifactUploadResult
9+
{
10+
/// <summary>
11+
/// The GUID that represents the ID of the artifact in our cloud storage database. Can be used to download or deploy the artifact.
12+
/// </summary>
13+
[JsonProperty("artifactId")]
14+
public string ArtifactId { get; set; }
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
namespace Skyline.DataMiner.Sdk.CatalogService
2+
{
3+
using System;
4+
using System.Net.Http;
5+
6+
/// <summary>
7+
/// Creates instances of <see cref="ICatalogService"/> to communicate with the Skyline DataMiner Catalog (https://catalog.dataminer.services/).
8+
/// </summary>
9+
public static class CatalogServiceFactory
10+
{
11+
/// <summary>
12+
/// Creates instances of <see cref="ICatalogService"/> to communicate with the Skyline DataMiner Catalog (https://catalog.dataminer.services/) using HTTP for communication.
13+
/// </summary>
14+
/// <param name="httpClient">An instance of <see cref="HttpClient"/> used for communication with the catalog.</param>
15+
/// <returns>An instance of <see cref="ICatalogService"/> to communicate with the Skyline DataMiner Catalog (https://catalog.dataminer.services/).</returns>
16+
public static ICatalogService CreateWithHttp(HttpClient httpClient)
17+
{
18+
var environment = Environment.GetEnvironmentVariable("Skyline-deploy-action-namespace");
19+
20+
string apiBaseUrl;
21+
if (environment != null)
22+
{
23+
apiBaseUrl = $"https://api-{environment}.dataminer.services/{environment}";
24+
}
25+
else
26+
{
27+
apiBaseUrl = "https://api.dataminer.services";
28+
}
29+
30+
httpClient.BaseAddress = new Uri($"{apiBaseUrl}/");
31+
return new HttpCatalogService(httpClient);
32+
}
33+
}
34+
}
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
namespace Skyline.DataMiner.Sdk.CatalogService
2+
{
3+
using System;
4+
using System.IO;
5+
using System.Net;
6+
using System.Net.Http;
7+
using System.Net.Http.Headers;
8+
using System.Security.Authentication;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
12+
using Newtonsoft.Json;
13+
14+
internal sealed class HttpCatalogService : ICatalogService, IDisposable
15+
{
16+
/// <summary>
17+
/// Artifact information returned from uploading an artifact to the catalog using the non-volatile upload.
18+
/// </summary>
19+
private sealed class CatalogUploadResult
20+
{
21+
[JsonProperty("catalogId")]
22+
public string CatalogId { get; set; }
23+
24+
[JsonProperty("catalogVersionNumber")]
25+
public string CatalogVersionNumber { get; set; }
26+
27+
[JsonProperty("azureStorageId")]
28+
public string AzureStorageId { get; set; }
29+
}
30+
31+
32+
private const string RegistrationPath = "api/key-catalog/v2-0/catalogs/register";
33+
private const string VersionUploadPathEnd = "/register/version";
34+
private const string VersionUploadPathStart = "api/key-catalog/v2-0/catalogs/";
35+
private readonly HttpClient _httpClient;
36+
37+
public HttpCatalogService(HttpClient httpClient)
38+
{
39+
_httpClient = httpClient;
40+
}
41+
42+
public void Dispose()
43+
{
44+
_httpClient.Dispose();
45+
}
46+
47+
public async Task<ArtifactUploadResult> RegisterCatalogAsync(byte[] catalogDetailsZip, string key, CancellationToken cancellationToken)
48+
{
49+
using (var formData = new MultipartFormDataContent())
50+
{
51+
formData.Headers.Add("Ocp-Apim-Subscription-Key", key);
52+
53+
// Add file
54+
using (MemoryStream ms = new MemoryStream(catalogDetailsZip))
55+
{
56+
ms.Write(catalogDetailsZip, 0, catalogDetailsZip.Length);
57+
ms.Position = 0;
58+
formData.Add(new StreamContent(ms), "file", "catalogDetails.zip");
59+
60+
61+
// Make PUT request
62+
var response = await _httpClient.PutAsync(RegistrationPath, formData, cancellationToken).ConfigureAwait(false);
63+
64+
// Get the response body
65+
var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
66+
67+
if (response.IsSuccessStatusCode)
68+
{
69+
var returnedResult = JsonConvert.DeserializeObject<CatalogUploadResult>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
70+
return new ArtifactUploadResult() { ArtifactId = returnedResult.AzureStorageId };
71+
}
72+
73+
if (response.StatusCode is HttpStatusCode.Forbidden || response.StatusCode is HttpStatusCode.Unauthorized)
74+
{
75+
throw new AuthenticationException($"The registration api returned a {response.StatusCode} response. Body: {body}");
76+
}
77+
78+
throw new InvalidOperationException($"The registration api returned a {response.StatusCode} response. Body: {body}");
79+
}
80+
}
81+
}
82+
83+
public async Task<ArtifactUploadResult> UploadVersionAsync(byte[] package, string fileName, string key, string catalogId, string version, string description, CancellationToken cancellationToken)
84+
{
85+
if (String.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version));
86+
87+
string versionUploadPath = $"{VersionUploadPathStart}{catalogId}{VersionUploadPathEnd}";
88+
using (var formData = new MultipartFormDataContent())
89+
{
90+
formData.Headers.Add("Ocp-Apim-Subscription-Key", key);
91+
92+
// Add the package (zip file) to the form data
93+
using (MemoryStream ms = new MemoryStream(package))
94+
{
95+
ms.Position = 0; // Reset the stream position after writing
96+
97+
// Set up StreamContent with correct headers for the file
98+
var fileContent = new StreamContent(ms);
99+
100+
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
101+
{
102+
Name = "\"file\"",
103+
FileName = "\"" + fileName + "\""
104+
};
105+
formData.Add(fileContent);
106+
107+
108+
// Add version information to the form data
109+
formData.Add(new StringContent(version), "versionNumber");
110+
formData.Add(new StringContent(description), "versionDescription");
111+
112+
// Log the info for debugging
113+
string logInfo = $"name {fileName} --versionNumber {version} --versionDescription {description}";
114+
115+
// Make the HTTP POST request
116+
var response = await _httpClient.PostAsync(versionUploadPath, formData, cancellationToken).ConfigureAwait(false);
117+
118+
// Read and log the response body
119+
var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
120+
121+
if (response.IsSuccessStatusCode)
122+
{
123+
var returnedResult = JsonConvert.DeserializeObject<CatalogUploadResult>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
124+
return new ArtifactUploadResult() { ArtifactId = returnedResult.AzureStorageId };
125+
}
126+
127+
if (response.StatusCode is HttpStatusCode.Forbidden || response.StatusCode is HttpStatusCode.Unauthorized)
128+
{
129+
throw new AuthenticationException($"The version upload api returned a {response.StatusCode} response. Body: {body}");
130+
}
131+
132+
throw new InvalidOperationException($"The version upload api returned a {response.StatusCode} response. Body: {body}");
133+
}
134+
}
135+
}
136+
}
137+
}

Sdk/CatalogService/ICatalogService.cs

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace Skyline.DataMiner.Sdk.CatalogService
2+
{
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
/// <summary>
7+
/// Service interface used to actually upload and artifact.
8+
/// </summary>
9+
public interface ICatalogService
10+
{
11+
/// <summary>
12+
/// Registers the catalog by uploading the catalog details as a zip file.
13+
/// </summary>
14+
/// <param name="catalogDetailsZip">A byte array containing the zipped catalog details.</param>
15+
/// <param name="key">A unique token used for authentication.</param>
16+
/// <param name="cancellationToken">A token used to cancel the ongoing registration if needed.</param>
17+
/// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous operation, returning an <see cref="ArtifactUploadResult"/>.</returns>
18+
Task<ArtifactUploadResult> RegisterCatalogAsync(byte[] catalogDetailsZip, string key, CancellationToken cancellationToken);
19+
20+
/// <summary>
21+
/// Uploads a specific version of the artifact to the catalog.
22+
/// </summary>
23+
/// <param name="package">A byte array containing the package content.</param>
24+
/// <param name="fileName">The name of the file being uploaded.</param>
25+
/// <param name="key">A unique token used for authentication.</param>
26+
/// <param name="catalogId">The unique catalog identifier for the artifact.</param>
27+
/// <param name="version">The version of the artifact being uploaded.</param>
28+
/// <param name="description">A description of the artifact version.</param>
29+
/// <param name="cancellationToken">A token used to cancel the ongoing upload if needed.</param>
30+
/// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous operation, returning an <see cref="ArtifactUploadResult"/>.</returns>
31+
Task<ArtifactUploadResult> UploadVersionAsync(byte[] package, string fileName, string key, string catalogId, string version, string description, CancellationToken cancellationToken);
32+
}
33+
}

Sdk/Sdk.csproj

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<BuildOutputTargetFolder>Sdk</BuildOutputTargetFolder>
2424
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
2525
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
26-
<PackageVersion>0.1.11</PackageVersion>
26+
<PackageVersion>0.2.4</PackageVersion>
2727
</PropertyGroup>
2828

2929
<ItemGroup>
@@ -36,6 +36,8 @@
3636

3737
<ItemGroup>
3838
<PackageReference Include="Microsoft.Build.Tasks.Core" VersionOverride="$(MicrosoftBuildMinimumPackageVersion)" PrivateAssets="all" ExcludeAssets="Runtime" IncludeAssets="compile; build; native; contentfiles; analyzers; buildtransitive" Version="14.3.0" />
39+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.1" />
40+
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.0" />
3941
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" PrivateAssets="all" />
4042
<PackageReference Include="Skyline.DataMiner.CICD.Common" Version="1.0.5-alpha" PrivateAssets="all" />
4143
<PackageReference Include="Skyline.DataMiner.CICD.Parsers.Common" Version="1.0.13-foxtrot" PrivateAssets="all" />

Sdk/Sdk/Sdk.targets

+20
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" Condition=" '$(CommonTargetsPath)' == '' " />
44

55
<!-- Declare which tasks will be used later -->
6+
<UsingTask TaskName="Skyline.DataMiner.Sdk.Tasks.PublishToCatalog" AssemblyFile="$(MSBuildThisFileDirectory)\netstandard2.0\Skyline.DataMiner.Sdk.dll"/>
67
<UsingTask TaskName="Skyline.DataMiner.Sdk.Tasks.CatalogInformation" AssemblyFile="$(MSBuildThisFileDirectory)\netstandard2.0\Skyline.DataMiner.Sdk.dll"/>
78
<UsingTask TaskName="Skyline.DataMiner.Sdk.Tasks.DmappCreation" AssemblyFile="$(MSBuildThisFileDirectory)\netstandard2.0\Skyline.DataMiner.Sdk.dll"/>
89

@@ -30,4 +31,23 @@
3031
PackageVersion="$(PackageVersion)"
3132
/>
3233
</Target>
34+
35+
<Target Name="Publish" Condition="'$(GenerateDataMinerPackage)' == 'true'" DependsOnTargets="$(ConditionalBuildTarget)">
36+
<!-- Replaces the default publish steps -->
37+
<Message Text="Publishing to catalog.dataminer.services...." Importance="high" />
38+
<PublishToCatalog
39+
ProjectDirectory="$(MSBuildProjectDirectory)"
40+
BaseOutputPath="$(BaseOutputPath)"
41+
Configuration="$(Configuration)"
42+
PackageId="$(PackageId)"
43+
PackageVersion="$(PackageVersion)"
44+
VersionComment="$(VersionComment)"
45+
CatalogPublishKeyName="$(CatalogPublishKeyName)"
46+
UserSecretsId="$(UserSecretsId)"
47+
/>
48+
</Target>
49+
50+
<PropertyGroup>
51+
<ConditionalBuildTarget Condition="'$(NoBuild)' != 'true'">Build</ConditionalBuildTarget>
52+
</PropertyGroup>
3353
</Project>

Sdk/Tasks/CatalogInformation.cs

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public override bool Execute()
6666
string destinationFilePath = fs.Path.Combine(baseLocation, Configuration, $"{PackageId}.{PackageVersion}.CatalogInformation.zip");
6767
fs.Directory.CreateDirectory(fs.Path.GetDirectoryName(destinationFilePath));
6868

69+
fs.File.DeleteFile(destinationFilePath);
6970
ZipFile.CreateFromDirectory(catalogInformationFolder, destinationFilePath, CompressionLevel.Optimal, includeBaseDirectory: false);
7071

7172
Log.LogMessage(MessageImportance.Low, $"CatalogInformation zipped to {destinationFilePath}");

0 commit comments

Comments
 (0)