From 416f2594caa1628584e24ea8eaaac58c72af5736 Mon Sep 17 00:00:00 2001 From: Per Kops Date: Wed, 5 Jun 2024 13:12:57 +0200 Subject: [PATCH 1/4] feat: add ConfigurationContentProvider --- .../Providers/ConfigurationContentProvider.cs | 71 +++++++++++++++++++ .../IConfigurationContentProvider.cs | 12 ++++ 2 files changed, 83 insertions(+) create mode 100644 src/Atc.Azure.IoT/Providers/ConfigurationContentProvider.cs create mode 100644 src/Atc.Azure.IoT/Providers/IConfigurationContentProvider.cs diff --git a/src/Atc.Azure.IoT/Providers/ConfigurationContentProvider.cs b/src/Atc.Azure.IoT/Providers/ConfigurationContentProvider.cs new file mode 100644 index 0000000..95c2f5b --- /dev/null +++ b/src/Atc.Azure.IoT/Providers/ConfigurationContentProvider.cs @@ -0,0 +1,71 @@ +namespace Atc.Azure.IoT.Providers; + +public sealed class ConfigurationContentProvider : IConfigurationContentProvider +{ + public async Task<(ConfigurationContent? ConfigurationContent, string? ErrorMessage)> GetConfigurationContent( + FileInfo deploymentManifestFileInfo, + CancellationToken cancellationToken) + { + if (!deploymentManifestFileInfo.Exists) + { + return (null, "Deployment Manifest file does not exist."); + } + + try + { + await using var fileStream = deploymentManifestFileInfo.OpenRead(); + return await GetConfigurationContent(fileStream, cancellationToken); + } + catch (Exception ex) + { + return (null, $"Error reading Deployment Manifest file: {ex.Message}"); + } + } + + public async Task<(ConfigurationContent? ConfigurationContent, string? ErrorMessage)> GetConfigurationContent( + Stream deploymentManifestFileStream, + CancellationToken cancellationToken) + { + var templateContent = await GetTemplateContent(deploymentManifestFileStream, cancellationToken); + if (templateContent is null) + { + return (null, "Deployment Manifest was not in proper format."); + } + + var configurationContent = GetConfigurationContentFromManifest(templateContent!); + if (configurationContent is null) + { + return (null, "Could not get ConfigurationContent from Deployment Manifest."); + } + + return (configurationContent, null); + } + + private static async Task GetTemplateContent( + Stream deploymentManifestFileStream, + CancellationToken cancellationToken) + { + using var reader = new StreamReader(deploymentManifestFileStream); + var content = await reader.ReadToEndAsync(cancellationToken); + + return content.IsFormatJson() + ? content + : null; + } + + /// + /// Gets the ConfigurationContent from the deployment manifest + /// + /// The deployment manifest + /// The deployment manifest as a + /// + /// We utilize Newtonsoft.Json here, because System.Text.Json does not work! + /// When de-serializing the edgeAgent - properties.desired, instead of "object-array", the serializer returns: + /// ValueKind = Object : " instead of { + /// and " instead of } (for the end). + /// + private static ConfigurationContent? GetConfigurationContentFromManifest( + string deploymentManifest) + => Newtonsoft.Json.JsonConvert.DeserializeObject( + deploymentManifest); +} \ No newline at end of file diff --git a/src/Atc.Azure.IoT/Providers/IConfigurationContentProvider.cs b/src/Atc.Azure.IoT/Providers/IConfigurationContentProvider.cs new file mode 100644 index 0000000..1f0c5a5 --- /dev/null +++ b/src/Atc.Azure.IoT/Providers/IConfigurationContentProvider.cs @@ -0,0 +1,12 @@ +namespace Atc.Azure.IoT.Providers; + +public interface IConfigurationContentProvider +{ + Task<(ConfigurationContent? ConfigurationContent, string? ErrorMessage)> GetConfigurationContent( + FileInfo deploymentManifestFileInfo, + CancellationToken cancellationToken); + + Task<(ConfigurationContent? ConfigurationContent, string? ErrorMessage)> GetConfigurationContent( + Stream deploymentManifestFileStream, + CancellationToken cancellationToken); +} \ No newline at end of file From 3e40566c9ba3677fd7e7353b290373a1ae408072 Mon Sep 17 00:00:00 2001 From: Per Kops Date: Fri, 7 Jun 2024 08:19:49 +0200 Subject: [PATCH 2/4] feat: wire-up ConfigurationContentProvider in DI container --- src/Atc.Azure.IoT/Extensions/ServiceCollectionExtensions.cs | 2 ++ src/Atc.Azure.IoT/GlobalUsings.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Atc.Azure.IoT/Extensions/ServiceCollectionExtensions.cs b/src/Atc.Azure.IoT/Extensions/ServiceCollectionExtensions.cs index f8d788a..4681e39 100644 --- a/src/Atc.Azure.IoT/Extensions/ServiceCollectionExtensions.cs +++ b/src/Atc.Azure.IoT/Extensions/ServiceCollectionExtensions.cs @@ -56,6 +56,8 @@ public static IServiceCollection ConfigureDeviceProvisioningServices( throw new InvalidOperationException($"Required service '{nameof(DeviceProvisioningServiceOptions)}' is not registered"); } + services.TryAddSingleton(); + services.AddSingleton(s => new DeviceProvisioningService( s.GetRequiredService(), deviceProvisioningServiceOptions)); diff --git a/src/Atc.Azure.IoT/GlobalUsings.cs b/src/Atc.Azure.IoT/GlobalUsings.cs index 2b2fa2b..32d56c7 100644 --- a/src/Atc.Azure.IoT/GlobalUsings.cs +++ b/src/Atc.Azure.IoT/GlobalUsings.cs @@ -8,6 +8,7 @@ global using Atc.Azure.IoT.Extractors; global using Atc.Azure.IoT.Models; global using Atc.Azure.IoT.Options; +global using Atc.Azure.IoT.Providers; global using Atc.Azure.IoT.Serialization.JsonConverters; global using Atc.Azure.IoT.Services.DeviceProvisioning; global using Atc.Azure.IoT.Services.IoTHub; From 0ca268c02ddbdc12b8e404ae25743ed09eac534f Mon Sep 17 00:00:00 2001 From: Per Kops Date: Fri, 7 Jun 2024 08:40:13 +0200 Subject: [PATCH 3/4] docs: update README.md with IConfigurationContentProvider --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3405cfc..bbd6f66 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,17 @@ IoT library, which contains common services for Azure IotHub, DeviceProvisioning - [Method](#method-1) - [Module Retrieval](#module-retrieval) - [Usage Example](#usage-example-3) -- [CLI](#cli) - - [Installation](#installation) + - [IConfigurationContentProvider](#iconfigurationcontentprovider) + - [Features](#features-4) + - [Methods](#methods-2) + - [Configuration Content Retrieval from File](#configuration-content-retrieval-from-file) + - [Configuration Content Retrieval from Stream](#configuration-content-retrieval-from-stream) + - [Usage Example](#usage-example-4) - [Update](#update) - [Usage](#usage) - [Option --help](#option---help) - [Atc.Azure.IoTEdge](#atcazureiotedge) - - [Features](#features-4) + - [Features](#features-5) - [Extensions](#extensions) - [Factories](#factories) - [Wrappers](#wrappers) @@ -276,6 +280,44 @@ else } ``` +## IConfigurationContentProvider + +The `IConfigurationContentProvider` is designed to assist in retrieving configuration content from deployment manifests in Azure IoT solutions. This interface provides methods for obtaining configuration content from both file and stream sources, ensuring flexibility and reliability in handling deployment manifests. + +### Features + +- **Configuration Content Retrieval**: Extract configuration content from deployment manifests, facilitating efficient deployment and management of IoT modules. + +### Methods + +#### Configuration Content Retrieval from File + +- `GetConfigurationContent(FileInfo deploymentManifestFileInfo, CancellationToken cancellationToken)`: Retrieves configuration content from a deployment manifest file. + +#### Configuration Content Retrieval from Stream + +- `GetConfigurationContent(Stream deploymentManifestFileStream, CancellationToken cancellationToken)`: Retrieves configuration content from a deployment manifest stream. + +### Usage Example + +Below is an example demonstrating how to use the `ConfigurationContentProvider` to retrieve configuration content from a deployment manifest file: + +```csharp +var configurationContentProvider = serviceProvider.GetRequiredService(); +var deploymentManifestFile = new FileInfo("path/to/deploymentManifest.json"); +var cancellationToken = new CancellationToken(); + +var (configurationContent, errorMessage) = await configurationContentProvider.GetConfigurationContent(deploymentManifestFile, cancellationToken); + +if (configurationContent != null) +{ + Console.WriteLine("Configuration content retrieved successfully."); +} +else +{ + Console.WriteLine($"Failed to retrieve configuration content: {errorMessage}"); +} + # CLI [![NuGet Version](https://img.shields.io/nuget/v/atc-azure-iot.svg?logo=nuget&style=for-the-badge)](https://www.nuget.org/packages/atc-azure-iot) From 1982ce0f9d16b51190eda3f9feeff5ea4cd6b8ea Mon Sep 17 00:00:00 2001 From: Per Kops Date: Fri, 7 Jun 2024 08:40:32 +0200 Subject: [PATCH 4/4] chore: add empty lines in README.md to conform with markdown rules --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index bbd6f66..62ca4b6 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ The `IIoTHubService` is designed to facilitate communication with Azure IoT Hub, ### Methods #### Device Management + - `CreateDevice(string deviceId, bool edgeEnabled, CancellationToken cancellationToken)`: Create a new device with the option to enable as an edge device. - `DeleteDevice(string deviceId, CancellationToken cancellationToken)`: Delete a device from the IoT Hub. - `GetDevice(string deviceId, CancellationToken cancellationToken)`: Retrieve a specific device using its device ID. @@ -99,6 +100,7 @@ The `IIoTHubService` is designed to facilitate communication with Azure IoT Hub, - `GetDeviceTwins(bool onlyIncludeEdgeDevices)`: Retrieve all device twins, with an option to filter for only edge devices. #### Module Management + - `GetModuleTwin(string deviceId, string moduleId, CancellationToken cancellationToken)`: Retrieve a specific module twin. - `UpdateDesiredProperties(string deviceId, string moduleId, TwinCollection twinCollection, CancellationToken cancellationToken)`: Update desired properties on a module twin. - `RemoveModuleFromDevice(string deviceId, string moduleId, CancellationToken cancellationToken)`: Remove a module from an IoT device. @@ -149,6 +151,7 @@ The `IIoTHubModuleService` is tailored for direct interactions with IoT devices ### Method #### Direct Method Invocation + - `CallMethod(string deviceId, string moduleId, MethodParameterModel parameters, CancellationToken cancellationToken)`: This method sends a direct command to a specified module on a device. The `MethodParameterModel` allows for detailed specification of the command, and the operation returns a `MethodResultModel` that includes the status of the call and any resultant data in JSON format. ### Usage Example @@ -187,6 +190,7 @@ The `IDeviceProvisioningService` is designed to manage device enrollments within ### Methods #### Individual Enrollment Management + - `GetIndividualEnrollment(string registrationId, CancellationToken cancellationToken)`: Retrieves a specific enrollment using the registration ID. - `GetIndividualEnrollments(CancellationToken cancellationToken)`: Fetches all registered individual enrollments. - `CreateIndividualTpmEnrollment(string endorsementKey, string registrationId, string deviceId, Dictionary? tags, Dictionary? desiredProperties, CancellationToken cancellationToken)`: Creates or updates a TPM enrollment with specified parameters. @@ -257,6 +261,7 @@ The `IDeviceTwinModuleExtractor` is designed to aid in extracting module informa ### Method #### Module Retrieval + - `GetModuleFromEdgeAgentTwin(Twin twin, string moduleId)`: Extracts a module from the Edge Agent twin using the module identifier. This method is essential for operations needing detailed information about individual modules managed by the Edge Agent. ### Usage Example