From 34b728aa3764b5279674a182b6886e084144916d Mon Sep 17 00:00:00 2001 From: Jan Staelens Date: Wed, 30 Jul 2025 17:35:37 +0200 Subject: [PATCH 1/5] WIP - Waiting on Technical Writing to check the format of the Notice concerning Webpages Public items. --- Sdk/Tasks/CatalogInformation.cs | 80 ++- SdkTests/Tasks/DmappCreationTests.cs | 117 +++++ SdkTests/Test Files/Package 7/.editorconfig | 11 + .../Package 7/Directory.Build.props | 24 + .../Code Analysis/qaction-debug.ruleset | 355 +++++++++++++ .../Code Analysis/qaction-release.ruleset | 484 ++++++++++++++++++ .../Internal/Code Analysis/stylecop.json | 74 +++ .../CatalogInformation/Images/wip.png | Bin 0 -> 35405 bytes .../My Package/CatalogInformation/README.md | 3 + .../CatalogInformation/manifest.yml | 67 +++ .../Package 7/My Package/GettingStarted.md | 110 ++++ .../Package 7/My Package/My Package.cs | 36 ++ .../Package 7/My Package/My Package.csproj | 23 + .../Package 7/My Package/My Package.xml | 26 + .../PackageContent/CatalogReferences.xml | 20 + .../Skyline DataMiner/AboutThisFolder.md | 1 + .../Webpages/Public/MyDirectory/MyStuff.txt | 0 .../Webpages/Public/MyFile1.txt | 1 + .../Webpages/Public/XMLFile1.xml | 1 + .../Dashboards/AboutThisFolder.md | 1 + .../LowCodeApps/AboutThisFolder.md | 1 + .../PackageContent/ProjectReferences.xml | 9 + .../Test Files/Package 7/My Package/README.md | 1 + .../SetupContent/AboutThisFolder.md | 1 + SdkTests/Test Files/Package 7/Package 7.sln | 34 ++ 25 files changed, 1471 insertions(+), 9 deletions(-) create mode 100644 SdkTests/Test Files/Package 7/.editorconfig create mode 100644 SdkTests/Test Files/Package 7/Directory.Build.props create mode 100644 SdkTests/Test Files/Package 7/Internal/Code Analysis/qaction-debug.ruleset create mode 100644 SdkTests/Test Files/Package 7/Internal/Code Analysis/qaction-release.ruleset create mode 100644 SdkTests/Test Files/Package 7/Internal/Code Analysis/stylecop.json create mode 100644 SdkTests/Test Files/Package 7/My Package/CatalogInformation/Images/wip.png create mode 100644 SdkTests/Test Files/Package 7/My Package/CatalogInformation/README.md create mode 100644 SdkTests/Test Files/Package 7/My Package/CatalogInformation/manifest.yml create mode 100644 SdkTests/Test Files/Package 7/My Package/GettingStarted.md create mode 100644 SdkTests/Test Files/Package 7/My Package/My Package.cs create mode 100644 SdkTests/Test Files/Package 7/My Package/My Package.csproj create mode 100644 SdkTests/Test Files/Package 7/My Package/My Package.xml create mode 100644 SdkTests/Test Files/Package 7/My Package/PackageContent/CatalogReferences.xml create mode 100644 SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/AboutThisFolder.md create mode 100644 SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/Webpages/Public/MyDirectory/MyStuff.txt create mode 100644 SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/Webpages/Public/MyFile1.txt create mode 100644 SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/Webpages/Public/XMLFile1.xml create mode 100644 SdkTests/Test Files/Package 7/My Package/PackageContent/Dashboards/AboutThisFolder.md create mode 100644 SdkTests/Test Files/Package 7/My Package/PackageContent/LowCodeApps/AboutThisFolder.md create mode 100644 SdkTests/Test Files/Package 7/My Package/PackageContent/ProjectReferences.xml create mode 100644 SdkTests/Test Files/Package 7/My Package/README.md create mode 100644 SdkTests/Test Files/Package 7/My Package/SetupContent/AboutThisFolder.md create mode 100644 SdkTests/Test Files/Package 7/Package 7.sln diff --git a/Sdk/Tasks/CatalogInformation.cs b/Sdk/Tasks/CatalogInformation.cs index 7e2fcf9..90f4d6f 100644 --- a/Sdk/Tasks/CatalogInformation.cs +++ b/Sdk/Tasks/CatalogInformation.cs @@ -5,14 +5,20 @@ namespace Skyline.DataMiner.Sdk.Tasks { using System; + using System.Collections.Generic; using System.Diagnostics; using System.IO.Compression; + using System.Linq; + using System.Threading; using Microsoft.Build.Framework; + using Microsoft.CodeAnalysis; using Skyline.DataMiner.CICD.FileSystem; using Skyline.DataMiner.Sdk.Helpers; + using static NuGet.Packaging.PackagingConstants; + using Task = Microsoft.Build.Utilities.Task; public class CatalogInformation : Task, ICancelableTask @@ -61,20 +67,33 @@ public override bool Execute() return true; } - string outputDirectory = BuildOutputHandler.GetOutputPath(Output, ProjectDirectory); - string destinationFilePath = fs.Path.Combine(outputDirectory, $"{PackageId}.{PackageVersion}.CatalogInformation.zip"); + // make a temporary directory to work in and make changes + string tempDirectory = FileSystem.Instance.Directory.CreateTemporaryDirectory(); + try + { + FileSystem.Instance.Directory.CopyRecursive(catalogInformationFolder, tempDirectory); + + AddOfficialNotices(fs, tempDirectory); - fs.File.DeleteFile(destinationFilePath); - ZipFile.CreateFromDirectory(catalogInformationFolder, destinationFilePath, CompressionLevel.Optimal, includeBaseDirectory: false); + string outputDirectory = BuildOutputHandler.GetOutputPath(Output, ProjectDirectory); + string destinationFilePath = fs.Path.Combine(outputDirectory, $"{PackageId}.{PackageVersion}.CatalogInformation.zip"); - Log.LogMessage(MessageImportance.High, $"Successfully created zip '{destinationFilePath}'."); + fs.File.DeleteFile(destinationFilePath); + ZipFile.CreateFromDirectory(tempDirectory, destinationFilePath, CompressionLevel.Optimal, includeBaseDirectory: false); - if (cancel) + Log.LogMessage(MessageImportance.High, $"Successfully created zip '{destinationFilePath}'."); + + if (cancel) + { + return false; + } + + return !Log.HasLoggedErrors; + } + finally { - return false; + FileSystem.Instance.Directory.DeleteDirectory(tempDirectory); } - - return !Log.HasLoggedErrors; } catch (Exception e) { @@ -87,5 +106,48 @@ public override bool Execute() Log.LogMessage(MessageImportance.High, $"Catalog information creation for '{PackageId}' took {timer.ElapsedMilliseconds} ms."); } } + + private void AddOfficialNotices(IFileSystem fs, string catalogInformationFolder) + { + // example D:\GITHUB\Skyline-QAOps\Skyline-QAOps-Package\PackageContent\CompanionFiles + var webpagesPublicDirectory = fs.Path.Combine(ProjectDirectory, "PackageContent", "CompanionFiles", "Skyline DataMiner", "Webpages", "Public"); + if (fs.Directory.Exists(webpagesPublicDirectory)) + { + // Get all files and folders directly under webpagesPublicDirectory + var entries = fs.Directory + .EnumerateDirectories(webpagesPublicDirectory) + .Select(path => fs.Path.GetFileName(path)) + .OrderBy(name => name) + .ToList(); + + entries.AddRange(fs.Directory + .EnumerateFiles(webpagesPublicDirectory) + .Select(path => fs.Path.GetFileName(path)) + .OrderBy(name => name) + .ToList()); + + if (entries.Count > 0) + { + var readmeFilePath = fs.Path.Combine(catalogInformationFolder, "README.md"); + if (fs.File.Exists(readmeFilePath)) + { + var noticeLines = new List + { + "", + "", + "> [!IMPORTANT]", + "> * For DataMiner versions earlier than 10.5.10, this package includes files located in `Skyline DataMiner/ Webpages / Public` that are **not automatically deployed** to all agents in a DataMiner Cluster.", + "> * To ensure proper functionality across the entire cluster, you must manually copy the following files and folders to the corresponding location on each agent after installation:" + }; + + noticeLines.AddRange(entries.Select(e => $">\t* `{e}`")); + noticeLines.Add(""); // final newline for clean formatting + + var noticeText = string.Join(Environment.NewLine, noticeLines); + fs.File.AppendAllText(readmeFilePath, noticeText); + } + } + } + } } } \ No newline at end of file diff --git a/SdkTests/Tasks/DmappCreationTests.cs b/SdkTests/Tasks/DmappCreationTests.cs index 07b5067..6d44de8 100644 --- a/SdkTests/Tasks/DmappCreationTests.cs +++ b/SdkTests/Tasks/DmappCreationTests.cs @@ -1,5 +1,7 @@ namespace SdkTests.Tasks { + using System.IO.Compression; + using FluentAssertions; using Microsoft.Build.Framework; @@ -95,5 +97,120 @@ public void ExecuteTest_Package6() FileSystem.Instance.Directory.DeleteDirectory(tempDirectory); } } + + [TestMethod] + public void ExecuteCatalogInformation_NoNotice() + { + string tempDirectory = FileSystem.Instance.Directory.CreateTemporaryDirectory(); + try + { + string projectDir = FileSystem.Instance.Path.Combine(TestHelper.GetTestFilesDirectory(), "Package 6", "My Package"); + + CatalogInformation info = new CatalogInformation() + { + ProjectDirectory = projectDir, + Output = tempDirectory, + PackageId = "My Package", + PackageVersion = "1.0.0", + BuildEngine = buildEngine.Object + }; + + string expectedDestinationFilePath = FileSystem.Instance.Path.Combine( + tempDirectory, + BuildOutputHandler.BuildDirectoryName, + $"{info.PackageId}.{info.PackageVersion}.CatalogInformation.zip"); + + // LOAD original README.md + string expectedReadmePath = FileSystem.Instance.Path.Combine(projectDir, "CatalogInformation", "README.md"); + FileSystem.Instance.File.Exists(expectedReadmePath).Should().BeTrue("expected README.md must exist"); + string expectedReadmeContent = FileSystem.Instance.File.ReadAllText(expectedReadmePath); + + // Act + bool result = info.Execute(); + errors.Should().BeEmpty(); + result.Should().BeTrue(); + FileSystem.Instance.File.Exists(expectedDestinationFilePath).Should().BeTrue(); + + // UNZIP to a temporary folder + string unzipDir = FileSystem.Instance.Path.Combine(tempDirectory, "unzipped"); + ZipFile.ExtractToDirectory(expectedDestinationFilePath, unzipDir); + + // FIND README.md inside the unzipped content + string[] readmeFiles = Directory.GetFiles(unzipDir, "README.md", SearchOption.AllDirectories); + readmeFiles.Length.Should().Be(1, "there should be exactly one README.md in the zipped output"); + string actualReadmeContent = File.ReadAllText(readmeFiles[0]); + + // COMPARE + actualReadmeContent.Should().Be(expectedReadmeContent, "README.md content in zip should match source"); + } + finally + { + FileSystem.Instance.Directory.DeleteDirectory(tempDirectory); + } + } + + + [TestMethod] + public void ExecuteCatalogInformation_WithNotice() + { + string tempDirectory = FileSystem.Instance.Directory.CreateTemporaryDirectory(); + try + { + // Arrange + string projectDir = FileSystem.Instance.Path.Combine(TestHelper.GetTestFilesDirectory(), "Package 7", "My Package"); + + CatalogInformation info = new CatalogInformation() + { + ProjectDirectory = projectDir, + Output = tempDirectory, + PackageId = "My Package", + PackageVersion = "1.0.0", + BuildEngine = buildEngine.Object + }; + + string expectedDestinationFilePath = FileSystem.Instance.Path.Combine( + tempDirectory, + BuildOutputHandler.BuildDirectoryName, + $"{info.PackageId}.{info.PackageVersion}.CatalogInformation.zip"); + + // LOAD original README.md + string expectedReadmePath = FileSystem.Instance.Path.Combine(projectDir, "CatalogInformation", "README.md"); + FileSystem.Instance.File.Exists(expectedReadmePath).Should().BeTrue("expected README.md must exist"); + string expectedReadmeContent = FileSystem.Instance.File.ReadAllText(expectedReadmePath); + + // Act + bool result = info.Execute(); + errors.Should().BeEmpty(); + result.Should().BeTrue(); + FileSystem.Instance.File.Exists(expectedDestinationFilePath).Should().BeTrue(); + + // UNZIP to a temporary folder + string unzipDir = FileSystem.Instance.Path.Combine(tempDirectory, "unzipped"); + ZipFile.ExtractToDirectory(expectedDestinationFilePath, unzipDir); + + // FIND README.md inside the unzipped content + string[] readmeFiles = Directory.GetFiles(unzipDir, "README.md", SearchOption.AllDirectories); + readmeFiles.Length.Should().Be(1, "there should be exactly one README.md in the zipped output"); + string actualReadmeContent = File.ReadAllText(readmeFiles[0]); + string expectedOriginalReadmeContent = FileSystem.Instance.File.ReadAllText(expectedReadmePath); + + // Normalize line endings to \n and trim + string Normalize(string input) => + input.Replace("\r\n", "\n").Replace("\r", "\n").TrimEnd(); + + string expectedWithNotice = expectedOriginalReadmeContent + + "\n## Important notice\n\n" + + "> ⚠️ **Warning!** For DataMiner version < 10.5.10, this package contains files within `Skyline DataMiner/Webpages/Public` that do not automatically install on every agent in a DataMiner Cluster.\n" + + "> After installation, the following files and folders must be manually copied to every agent in the cluster:\n\n" + + "- `DoStuff`"; + + Normalize(actualReadmeContent) + .Should().Be(Normalize(expectedWithNotice), "README.md content in zip should match source with a notice appended to it."); + } + finally + { + FileSystem.Instance.Directory.DeleteDirectory(tempDirectory); + } + } } } \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/.editorconfig b/SdkTests/Test Files/Package 7/.editorconfig new file mode 100644 index 0000000..a612a7a --- /dev/null +++ b/SdkTests/Test Files/Package 7/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = tab +indent_size = 4 +tab_width = 4 +end_of_line = crlf +trim_trailing_whitespace = true + +[*.cs] +dotnet_sort_system_directives_first = true \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/Directory.Build.props b/SdkTests/Test Files/Package 7/Directory.Build.props new file mode 100644 index 0000000..ef01745 --- /dev/null +++ b/SdkTests/Test Files/Package 7/Directory.Build.props @@ -0,0 +1,24 @@ + + + full + ..\Internal\Code Analysis\qaction-debug.ruleset + + + pdbonly + ..\Internal\Code Analysis\qaction-release.ruleset + + + + Properties\stylecop.json + + + Properties\.editorconfig + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/Internal/Code Analysis/qaction-debug.ruleset b/SdkTests/Test Files/Package 7/Internal/Code Analysis/qaction-debug.ruleset new file mode 100644 index 0000000..26e7d46 --- /dev/null +++ b/SdkTests/Test Files/Package 7/Internal/Code Analysis/qaction-debug.ruleset @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/Internal/Code Analysis/qaction-release.ruleset b/SdkTests/Test Files/Package 7/Internal/Code Analysis/qaction-release.ruleset new file mode 100644 index 0000000..de0890a --- /dev/null +++ b/SdkTests/Test Files/Package 7/Internal/Code Analysis/qaction-release.ruleset @@ -0,0 +1,484 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/Internal/Code Analysis/stylecop.json b/SdkTests/Test Files/Package 7/Internal/Code Analysis/stylecop.json new file mode 100644 index 0000000..b2d519d --- /dev/null +++ b/SdkTests/Test Files/Package 7/Internal/Code Analysis/stylecop.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "indentation": { + "useTabs": true, + "tabSize": 4 + }, + "namingRules": { + "allowCommonHungarianPrefixes": false, + "allowedHungarianPrefixes": [ + "ab", + "ac", + "ad", + "af", + "ai", + "al", + "ao", + "as", + "ax", + "b", + "by", + "c", + "d", + "dt", + "e", + "f", + "hs", + "i", + "ip", + "is", + "l", + "lb", + "lc", + "ld", + "lf", + "li", + "ll", + "lo", + "ls", + "lx", + "my", + "no", + "o", + "qb", + "qc", + "qd", + "qf", + "qi", + "ql", + "qo", + "qs", + "qx", + "rx", + "s", + "sb", + "sc", + "sd", + "sf", + "sh", + "si", + "sl", + "so", + "ss", + "sx", + "to", + "ts", + "tx", + "ui", + "ul", + "x" + ] + } + } +} diff --git a/SdkTests/Test Files/Package 7/My Package/CatalogInformation/Images/wip.png b/SdkTests/Test Files/Package 7/My Package/CatalogInformation/Images/wip.png new file mode 100644 index 0000000000000000000000000000000000000000..2413c2d555af857ec8365081228ce2b3b663939b GIT binary patch literal 35405 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 literal 0 HcmV?d00001 diff --git a/SdkTests/Test Files/Package 7/My Package/CatalogInformation/README.md b/SdkTests/Test Files/Package 7/My Package/CatalogInformation/README.md new file mode 100644 index 0000000..0ef542d --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/CatalogInformation/README.md @@ -0,0 +1,3 @@ +# My Package + +![WIP](./Images/wip.png) \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/My Package/CatalogInformation/manifest.yml b/SdkTests/Test Files/Package 7/My Package/CatalogInformation/manifest.yml new file mode 100644 index 0000000..1bd7ec5 --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/CatalogInformation/manifest.yml @@ -0,0 +1,67 @@ +# [Required] +# Possible values for the Catalog item that can be deployed on a DataMiner System: +# - Automation: If the Catalog item is a general-purpose DataMiner Automation script. +# - Ad Hoc Data Source: If the Catalog item is a DataMiner Automation script designed for an ad hoc data source integration. +# - ChatOps Extension: If the Catalog item is a DataMiner Automation script designed as a ChatOps extension. +# - Connector: If the Catalog item is a DataMiner XML connector. +# - Custom Solution: If the Catalog item is a DataMiner Solution. +# - Data Query: If the Catalog item is a GQI data query. +# - Data Transformer: Includes a data transformer that enables you to modify data using a GQI data query before making it available to users in low-code apps or dashboards. +# - Dashboard: If the Catalog item is a DataMiner dashboard. +# - DevTool: If the Catalog item is a DevTool. +# - Learning & Sample: If the Catalog item is a sample. +# - Product Solution: If the Catalog item is a DataMiner Solution that is an out-of-the-box solution for a specific product. +# - Scripted Connector: If the Catalog item is a DataMiner scripted connector. +# - Standard Solution: If the Catalog item is a DataMiner Solution that is an out-of-the-box solution for a specific use case or application. +# - System Health: If the Catalog item is intended to monitor the health of a system. +# - User-Defined API: If the Catalog item is a DataMiner Automation script designed as a user-defined API. +# - Visual Overview: If the Catalog item is a Microsoft Visio design. +# +type: Custom Solution +# [Required] +# The ID of the Catalog item. +# All registered versions for the same ID are shown together in the Catalog. +# This ID cannot be changed. +# If the ID is not filled in, the registration will fail with HTTP status code 500. +# If the ID is filled in but does not exist yet, a new Catalog item will be registered with this ID. +# If the ID is filled in but does exist, properties of the item will be overwritten. +# Must be a valid GUID. +id: e9ef5f60-fa3f-4081-b879-39ffb031255a +# [Required] +# The human-friendly name of the Catalog item. +# Can be changed at any time. +# Max length: 100 characters. +# Cannot contain newlines. +# Cannot contain leading or trailing whitespace characters. +title: My Package +# [Optional] +# General information about the Catalog item. +# Max length: 100,000 characters +short_description: This is a custom solution for DataMiner. +# [Optional] +# A valid URL that points to the source code. +# A valid URL +# Max length: 2048 characters +# Note: When Skyline Communications Reusable GitHub workflows are used, this will be automatically filled in. +source_code_url: +# [Optional] +# A valid URL that points to documentation. +# A valid URL +# Max length: 2048 characters +# Note: When Skyline Communications Reusable GitHub workflows are used, this will be automatically filled in. +documentation_url: +# [Optional] +# People who are responsible for this Catalog item. Might be developers, but this is not required. +# Format: 'name (URL)' +# The name is required; max 256 characters. +# The email and URL are optional, and should be in valid email/URL formats. +owners: + - name: 'MOD' +# [Optional] +# Tags that allow you to categorize your Catalog items. +# Max number of tags: 5 +# Max length: 50 characters. +# Cannot contain newlines. +# Cannot contain leading or trailing whitespace characters. +tags: + - dataminer diff --git a/SdkTests/Test Files/Package 7/My Package/GettingStarted.md b/SdkTests/Test Files/Package 7/My Package/GettingStarted.md new file mode 100644 index 0000000..ab44a75 --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/GettingStarted.md @@ -0,0 +1,110 @@ +# Getting Started with Skyline DataMiner DevOps + +Welcome to the Skyline DataMiner DevOps environment! +This quick-start guide will help you get up and running. +For more details and comprehensive instructions, please visit [DataMiner Docs](https://docs.dataminer.services/). + +## Creating a DataMiner Application Package + +This project is configured to create a `.dmapp` file every time you build the project. +When you compile or build the project, you will find the generated `.dmapp` in the standard output folder, typically the `bin` folder of your project. + +When you publish the project, a corresponding item will be created in the online DataMiner Catalog. + +## The DataMiner Package Project + +This project is designed to create multi-artifact packages in a straightforward manner. + +### Adding Extra Artifacts in the Same Solution + +You can right-click the solution and select **Add** and then **New Project**. This will allow you to select DataMiner project templates (e.g. adding additional Automation scripts). + +> [!NOTE] +> Connectors are currently not supported. + +Every **Skyline.DataMiner.SDK** project, except other DataMiner package projects, will by default be included within the `.dmapp` created by this project. +You can customize this behavior using the **PackageContent/ProjectReferences.xml** file. This allows you to add filters to include or exclude projects as needed. + + + +### Importing from DataMiner + +You can import specific items directly from a DataMiner Agent: + +1. Connect to an Agent via **Extensions > DIS > DMA > Connect**. + +1. If your Agent is not listed, add it by going to **Extensions > DIS > Settings** and clicking **Add** on the DMA tab. + +1. Once connected, you can import specific DataMiner artifacts: in your **Solution Explorer**, navigate to folders such as **PackageContent/Dashboards** or **PackageContent/LowCodeApps**, right-click, select **Add**, and select **Import DataMiner Dashboard/Low Code App** or the equivalent. + +## Executing Additional Code on Installation + +Open the **My Package.cs** file to write custom installation code. Common actions include creating elements, services, or views. + +**Quick tip:** Type `clGetDms` in the `.cs` file and press **Tab** twice to insert a snippet that gives you access to the **IDms** classes, making DataMiner manipulation easier. + +## Does Your Installation Code Need Configuration Files? + +You can add configuration files (e.g. `.json`, `.xml`) to the **SetupContent** folder, which can be accessed during installation. + +Access them in your code using: + +```csharp +string setupContentPath = installer.GetSetupContentDirectory(); +``` + + +## Publishing to the Catalog + +This project was created with support for publishing to the DataMiner Catalog. +You can publish your artifact manually through Visual Studio or by setting up a CI/CD workflow. + +### Manual Publishing + +1. Obtain an **Organization Key** from [admin.dataminer.services](https://admin.dataminer.services/) with the following scopes: + - **Register Catalog items** + - **Read Catalog items** + +1. Securely store the key using Visual Studio User Secrets: + + 1. Right-click the project and select **Manage User Secrets**. + + 1. Add the key in the following format: + + ```json + { + "skyline": { + "sdk": { + "catalogpublishtoken": "MyKeyHere" + } + } + } + ``` + +1. Publish the package by right-clicking your project in Visual Studio and then selecting the **Publish** option. + + This will open a new window, where you will find a Publish button and a link where your item will eventually be registered. + +**Recommendation:** To safeguard the quality of your product, consider using a CI/CD setup to run **dotnet publish** only after passing quality checks. + +### Changing the Version + +1. Navigate to your project in Visual Studio, right-click, and select Properties. + +1. Search for Package Version. + +1. Adjust the value as needed. + +### Changing the Version - Alternative + +1. Navigate to your project in Visual Studio and double-click it. + +1. Adjust the "Version" XML tag to the version you want to register. + + ```xml + 1.0.1 + ``` diff --git a/SdkTests/Test Files/Package 7/My Package/My Package.cs b/SdkTests/Test Files/Package 7/My Package/My Package.cs new file mode 100644 index 0000000..f0b440a --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/My Package.cs @@ -0,0 +1,36 @@ +using System; + +using Skyline.AppInstaller; +using Skyline.DataMiner.Automation; +using Skyline.DataMiner.Net.AppPackages; + +/// +/// DataMiner Script Class. +/// +internal class Script +{ + /// + /// The script entry point. + /// + /// Provides access to the Automation engine. + /// Provides access to the installation context. + [AutomationEntryPoint(AutomationEntryPointType.Types.InstallAppPackage)] + public void Install(IEngine engine, AppInstallContext context) + { + try + { + engine.Timeout = new TimeSpan(0, 10, 0); + engine.GenerateInformation("Starting installation"); + var installer = new AppInstaller(Engine.SLNetRaw, context); + installer.InstallDefaultContent(); + + string setupContentPath = installer.GetSetupContentDirectory(); + + // Custom installation logic can be added here for each individual install package. + } + catch (Exception e) + { + engine.ExitFail($"Exception encountered during installation: {e}"); + } + } +} \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/My Package/My Package.csproj b/SdkTests/Test Files/Package 7/My Package/My Package.csproj new file mode 100644 index 0000000..47f95d0 --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/My Package.csproj @@ -0,0 +1,23 @@ + + + net48 + true + + + Package + True + 1.0.0 + Initial Version + + skyline:sdk:catalogpublishtoken + skyline:sdk:catalogdownloadtoken + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/My Package/My Package.xml b/SdkTests/Test Files/Package 7/My Package/My Package.xml new file mode 100644 index 0000000..c890fab --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/My Package.xml @@ -0,0 +1,26 @@ + + + My Package + + Automation + MOD + FALSE + + + + + + + + + + + + + \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/My Package/PackageContent/CatalogReferences.xml b/SdkTests/Test Files/Package 7/My Package/PackageContent/CatalogReferences.xml new file mode 100644 index 0000000..4b6f754 --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/PackageContent/CatalogReferences.xml @@ -0,0 +1,20 @@ + + + Microsoft Platform + + 1.1.3.25 + + + + Microsoft Platform + + 5.0.0 + + + + Microsoft Platform + + 6.0.0 + + + \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/AboutThisFolder.md b/SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/AboutThisFolder.md new file mode 100644 index 0000000..fdf2173 --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/AboutThisFolder.md @@ -0,0 +1 @@ +This folder can contain any file you would like to add under the Skyline DataMiner folder. Warning: it will overwrite any file with the same name. \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/Webpages/Public/MyDirectory/MyStuff.txt b/SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/Webpages/Public/MyDirectory/MyStuff.txt new file mode 100644 index 0000000..e69de29 diff --git a/SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/Webpages/Public/MyFile1.txt b/SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/Webpages/Public/MyFile1.txt new file mode 100644 index 0000000..05eac02 --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/Webpages/Public/MyFile1.txt @@ -0,0 +1 @@ +Hello World \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/Webpages/Public/XMLFile1.xml b/SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/Webpages/Public/XMLFile1.xml new file mode 100644 index 0000000..7dde50e --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/PackageContent/CompanionFiles/Skyline DataMiner/Webpages/Public/XMLFile1.xml @@ -0,0 +1 @@ + diff --git a/SdkTests/Test Files/Package 7/My Package/PackageContent/Dashboards/AboutThisFolder.md b/SdkTests/Test Files/Package 7/My Package/PackageContent/Dashboards/AboutThisFolder.md new file mode 100644 index 0000000..5ad682f --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/PackageContent/Dashboards/AboutThisFolder.md @@ -0,0 +1 @@ +This folder can contain .zip files containing dashboards exported from DataMiner. They will be imported during installation of a .dmapp. \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/My Package/PackageContent/LowCodeApps/AboutThisFolder.md b/SdkTests/Test Files/Package 7/My Package/PackageContent/LowCodeApps/AboutThisFolder.md new file mode 100644 index 0000000..0cf0cc3 --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/PackageContent/LowCodeApps/AboutThisFolder.md @@ -0,0 +1 @@ +This folder can contain .zip files containing low-code apps exported from DataMiner. They will be imported during installation of a .dmapp. \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/My Package/PackageContent/ProjectReferences.xml b/SdkTests/Test Files/Package 7/My Package/PackageContent/ProjectReferences.xml new file mode 100644 index 0000000..da65000 --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/PackageContent/ProjectReferences.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/My Package/README.md b/SdkTests/Test Files/Package 7/My Package/README.md new file mode 100644 index 0000000..98876b1 --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/README.md @@ -0,0 +1 @@ +# Technical Documentation for My Package \ No newline at end of file diff --git a/SdkTests/Test Files/Package 7/My Package/SetupContent/AboutThisFolder.md b/SdkTests/Test Files/Package 7/My Package/SetupContent/AboutThisFolder.md new file mode 100644 index 0000000..100db93 --- /dev/null +++ b/SdkTests/Test Files/Package 7/My Package/SetupContent/AboutThisFolder.md @@ -0,0 +1 @@ +This directory contains files that can be used by the installer, e.g. by the install script. The files will be put in the C:\Skyline DataMiner\AppPackages\Installed\.\SetupContent directory. diff --git a/SdkTests/Test Files/Package 7/Package 7.sln b/SdkTests/Test Files/Package 7/Package 7.sln new file mode 100644 index 0000000..3536f2c --- /dev/null +++ b/SdkTests/Test Files/Package 7/Package 7.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35514.174 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "My Package", "My Package\My Package.csproj", "{389C15E0-FFB3-40C3-94B6-8B99886F0A04}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Internal", "Internal", "{83D48836-5BD5-4084-A12A-C2F663BD9002}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Code Analysis", "Code Analysis", "{79CFE612-F7E5-40FD-A6AC-26FABE6091E2}" + ProjectSection(SolutionItems) = preProject + Internal\Code Analysis\qaction-debug.ruleset = Internal\Code Analysis\qaction-debug.ruleset + Internal\Code Analysis\qaction-release.ruleset = Internal\Code Analysis\qaction-release.ruleset + Internal\Code Analysis\stylecop.json = Internal\Code Analysis\stylecop.json + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {389C15E0-FFB3-40C3-94B6-8B99886F0A04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {389C15E0-FFB3-40C3-94B6-8B99886F0A04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {389C15E0-FFB3-40C3-94B6-8B99886F0A04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {389C15E0-FFB3-40C3-94B6-8B99886F0A04}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {79CFE612-F7E5-40FD-A6AC-26FABE6091E2} = {83D48836-5BD5-4084-A12A-C2F663BD9002} + EndGlobalSection +EndGlobal From a16ad904c5e8469226ea7f212838399db4a984b5 Mon Sep 17 00:00:00 2001 From: Jan Staelens Date: Thu, 31 Jul 2025 09:40:36 +0200 Subject: [PATCH 2/5] Reviewed my Technical Writing - fixed unit test. --- Sdk/Tasks/CatalogInformation.cs | 15 ++++++++++----- SdkTests/Tasks/DmappCreationTests.cs | 15 +++++++++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Sdk/Tasks/CatalogInformation.cs b/Sdk/Tasks/CatalogInformation.cs index 90f4d6f..c1a277c 100644 --- a/Sdk/Tasks/CatalogInformation.cs +++ b/Sdk/Tasks/CatalogInformation.cs @@ -14,6 +14,8 @@ namespace Skyline.DataMiner.Sdk.Tasks using Microsoft.Build.Framework; using Microsoft.CodeAnalysis; + using NuGet.Packaging; + using Skyline.DataMiner.CICD.FileSystem; using Skyline.DataMiner.Sdk.Helpers; @@ -110,19 +112,20 @@ public override bool Execute() private void AddOfficialNotices(IFileSystem fs, string catalogInformationFolder) { // example D:\GITHUB\Skyline-QAOps\Skyline-QAOps-Package\PackageContent\CompanionFiles + var pathToPublicDirectoryOnSystem = fs.Path.Combine(@"C:\", "Skyline DataMiner", "Webpages", "Public"); var webpagesPublicDirectory = fs.Path.Combine(ProjectDirectory, "PackageContent", "CompanionFiles", "Skyline DataMiner", "Webpages", "Public"); if (fs.Directory.Exists(webpagesPublicDirectory)) { // Get all files and folders directly under webpagesPublicDirectory var entries = fs.Directory .EnumerateDirectories(webpagesPublicDirectory) - .Select(path => fs.Path.GetFileName(path)) + .Select(path => fs.Path.Combine(pathToPublicDirectoryOnSystem, fs.Path.GetFileName(path))) .OrderBy(name => name) .ToList(); entries.AddRange(fs.Directory .EnumerateFiles(webpagesPublicDirectory) - .Select(path => fs.Path.GetFileName(path)) + .Select(path => fs.Path.Combine(pathToPublicDirectoryOnSystem, fs.Path.GetFileName(path))) .OrderBy(name => name) .ToList()); @@ -136,11 +139,13 @@ private void AddOfficialNotices(IFileSystem fs, string catalogInformationFolder) "", "", "> [!IMPORTANT]", - "> * For DataMiner versions earlier than 10.5.10, this package includes files located in `Skyline DataMiner/ Webpages / Public` that are **not automatically deployed** to all agents in a DataMiner Cluster.", - "> * To ensure proper functionality across the entire cluster, you must manually copy the following files and folders to the corresponding location on each agent after installation:" + ">", + $"> - For DataMiner versions prior to 10.5.10, this package includes files located in `{pathToPublicDirectoryOnSystem}` that are **not automatically deployed** to all agents in a DataMiner System.", + "> - To ensure proper functionality across the entire cluster, manually copy the following files and folders to the corresponding location on each agent after installation:", + ">", }; - noticeLines.AddRange(entries.Select(e => $">\t* `{e}`")); + noticeLines.AddRange(entries.Select(e => $"> - `{e}`")); noticeLines.Add(""); // final newline for clean formatting var noticeText = string.Join(Environment.NewLine, noticeLines); diff --git a/SdkTests/Tasks/DmappCreationTests.cs b/SdkTests/Tasks/DmappCreationTests.cs index 6d44de8..58c3099 100644 --- a/SdkTests/Tasks/DmappCreationTests.cs +++ b/SdkTests/Tasks/DmappCreationTests.cs @@ -199,10 +199,17 @@ string Normalize(string input) => input.Replace("\r\n", "\n").Replace("\r", "\n").TrimEnd(); string expectedWithNotice = expectedOriginalReadmeContent + - "\n## Important notice\n\n" + - "> ⚠️ **Warning!** For DataMiner version < 10.5.10, this package contains files within `Skyline DataMiner/Webpages/Public` that do not automatically install on every agent in a DataMiner Cluster.\n" + - "> After installation, the following files and folders must be manually copied to every agent in the cluster:\n\n" + - "- `DoStuff`"; + "\n\n" + + "> [!IMPORTANT]\n" + + ">\n" + + "> - For DataMiner versions prior to 10.5.10, this package includes files located in `C:\\Skyline DataMiner\\Webpages\\Public` that are **not automatically deployed** to all agents in a DataMiner System.\n" + + "> - To ensure proper functionality across the entire cluster, manually copy the following files and folders to the corresponding location on each agent after installation:\n" + + ">\n" + + "> - `C:\\Skyline DataMiner\\Webpages\\Public\\DoStuff`\n" + + "> - `C:\\Skyline DataMiner\\Webpages\\Public\\MyDirectory`\n" + + "> - `C:\\Skyline DataMiner\\Webpages\\Public\\MyFile1.txt`\n" + + "> - `C:\\Skyline DataMiner\\Webpages\\Public\\XMLFile1.xml`"; + Normalize(actualReadmeContent) .Should().Be(Normalize(expectedWithNotice), "README.md content in zip should match source with a notice appended to it."); From fd85b7b22dfa999068d34dd0d822a0da61358186 Mon Sep 17 00:00:00 2001 From: Jan Staelens Date: Thu, 31 Jul 2025 09:48:21 +0200 Subject: [PATCH 3/5] Sticky Bin folder had a previous folder that was empty. fixed. --- SdkTests/Tasks/DmappCreationTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/SdkTests/Tasks/DmappCreationTests.cs b/SdkTests/Tasks/DmappCreationTests.cs index 58c3099..e52da0a 100644 --- a/SdkTests/Tasks/DmappCreationTests.cs +++ b/SdkTests/Tasks/DmappCreationTests.cs @@ -205,7 +205,6 @@ string Normalize(string input) => "> - For DataMiner versions prior to 10.5.10, this package includes files located in `C:\\Skyline DataMiner\\Webpages\\Public` that are **not automatically deployed** to all agents in a DataMiner System.\n" + "> - To ensure proper functionality across the entire cluster, manually copy the following files and folders to the corresponding location on each agent after installation:\n" + ">\n" + - "> - `C:\\Skyline DataMiner\\Webpages\\Public\\DoStuff`\n" + "> - `C:\\Skyline DataMiner\\Webpages\\Public\\MyDirectory`\n" + "> - `C:\\Skyline DataMiner\\Webpages\\Public\\MyFile1.txt`\n" + "> - `C:\\Skyline DataMiner\\Webpages\\Public\\XMLFile1.xml`"; From 788adf15f4177976c0df580f007a414f76d7e57e Mon Sep 17 00:00:00 2001 From: Jan Staelens Date: Thu, 31 Jul 2025 12:08:32 +0200 Subject: [PATCH 4/5] Updated to Agent and Agents --- Sdk/Tasks/CatalogInformation.cs | 4 ++-- SdkTests/Tasks/DmappCreationTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sdk/Tasks/CatalogInformation.cs b/Sdk/Tasks/CatalogInformation.cs index c1a277c..d57f373 100644 --- a/Sdk/Tasks/CatalogInformation.cs +++ b/Sdk/Tasks/CatalogInformation.cs @@ -140,8 +140,8 @@ private void AddOfficialNotices(IFileSystem fs, string catalogInformationFolder) "", "> [!IMPORTANT]", ">", - $"> - For DataMiner versions prior to 10.5.10, this package includes files located in `{pathToPublicDirectoryOnSystem}` that are **not automatically deployed** to all agents in a DataMiner System.", - "> - To ensure proper functionality across the entire cluster, manually copy the following files and folders to the corresponding location on each agent after installation:", + $"> - For DataMiner versions prior to 10.5.10, this package includes files located in `{pathToPublicDirectoryOnSystem}` that are **not automatically deployed** to all Agents in a DataMiner System.", + "> - To ensure proper functionality across the entire cluster, manually copy the following files and folders to the corresponding location on each Agent after installation:", ">", }; diff --git a/SdkTests/Tasks/DmappCreationTests.cs b/SdkTests/Tasks/DmappCreationTests.cs index e52da0a..883a87a 100644 --- a/SdkTests/Tasks/DmappCreationTests.cs +++ b/SdkTests/Tasks/DmappCreationTests.cs @@ -202,8 +202,8 @@ string Normalize(string input) => "\n\n" + "> [!IMPORTANT]\n" + ">\n" + - "> - For DataMiner versions prior to 10.5.10, this package includes files located in `C:\\Skyline DataMiner\\Webpages\\Public` that are **not automatically deployed** to all agents in a DataMiner System.\n" + - "> - To ensure proper functionality across the entire cluster, manually copy the following files and folders to the corresponding location on each agent after installation:\n" + + "> - For DataMiner versions prior to 10.5.10, this package includes files located in `C:\\Skyline DataMiner\\Webpages\\Public` that are **not automatically deployed** to all Agents in a DataMiner System.\n" + + "> - To ensure proper functionality across the entire cluster, manually copy the following files and folders to the corresponding location on each Agent after installation:\n" + ">\n" + "> - `C:\\Skyline DataMiner\\Webpages\\Public\\MyDirectory`\n" + "> - `C:\\Skyline DataMiner\\Webpages\\Public\\MyFile1.txt`\n" + From 8f1080046bf64070bb7f962e236bf35da60d8f18 Mon Sep 17 00:00:00 2001 From: Jan Staelens Date: Fri, 1 Aug 2025 11:23:04 +0200 Subject: [PATCH 5/5] Updated to Core.AppPackages 3.0.2 --- Sdk/Sdk.csproj | 4 ++-- SdkTests/SdkTests.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sdk/Sdk.csproj b/Sdk/Sdk.csproj index 8ac1659..dbf8ff6 100644 --- a/Sdk/Sdk.csproj +++ b/Sdk/Sdk.csproj @@ -43,8 +43,8 @@ By integrating this SDK into your build process, you can easily generate install - - + + diff --git a/SdkTests/SdkTests.csproj b/SdkTests/SdkTests.csproj index 8790916..f4ad545 100644 --- a/SdkTests/SdkTests.csproj +++ b/SdkTests/SdkTests.csproj @@ -18,8 +18,8 @@ - - + +