diff --git a/changelog/content/experimental/unreleased.md b/changelog/content/experimental/unreleased.md
index c50da55dc..ce872da98 100644
--- a/changelog/content/experimental/unreleased.md
+++ b/changelog/content/experimental/unreleased.md
@@ -10,3 +10,4 @@ version: v1.6.0
- {{% tag added %}} Support for Kubernetes RBAC in Helm chart ([Helm Hub](https://hub.helm.sh/charts/promitor/promitor-agent-scraper) | [#951](https://github.com/tomkerkhove/promitor/issues/951))
- {{% tag added %}} Support for configuring Pod Security Policy in Helm Chart ([Helm Hub](https://hub.helm.sh/charts/promitor/promitor-agent-scraper) | [#952](https://github.com/tomkerkhove/promitor/issues/952))
- {{% tag added %}} Support for scraping Azure IoT Hub metrics ([docs](https://promitor.io/configuration/v1.x/metrics/iot-hub) | [#372](https://github.com/tomkerkhove/promitor/issues/372))
+- {{% tag added %}} Support for scraping Azure IoT Hub Device Provisioning Service (DPS) metrics ([docs](https://promitor.io/configuration/v1.x/metrics/iot-hub-device-provisioning-service) | [#1014](https://github.com/tomkerkhove/promitor/issues/1014))
diff --git a/docs/configuration/v1.x/metrics/index.md b/docs/configuration/v1.x/metrics/index.md
index ccc3ed05a..7d9ef63b7 100644
--- a/docs/configuration/v1.x/metrics/index.md
+++ b/docs/configuration/v1.x/metrics/index.md
@@ -129,6 +129,7 @@ We also provide a simplified way to scrape the following Azure resources:
- [Azure Database for PostgreSQL](postgresql)
- [Azure Function App](function-app)
- [Azure IoT Hub](iot-hub)
+- [Azure IoT Hub Device Provisioning Service (DPS)](iot-hub-device-provisioning-service)
- [Azure Network Interface](network-interface)
- [Azure Service Bus Queue](service-bus-queue)
- [Azure SQL Database](sql-database)
diff --git a/docs/configuration/v1.x/metrics/iot-hub-device-provisioning-service.md b/docs/configuration/v1.x/metrics/iot-hub-device-provisioning-service.md
new file mode 100644
index 000000000..32f1c260d
--- /dev/null
+++ b/docs/configuration/v1.x/metrics/iot-hub-device-provisioning-service.md
@@ -0,0 +1,35 @@
+---
+layout: default
+title: Azure IoT Hub Device Provisioning Service (DPS) Declaration
+---
+
+## Azure IoT Hub Device Provisioning Service (DPS) - ![Availability Badge](https://img.shields.io/badge/Available%20Starting-v1.6-green.svg)
+
+You can declare to scrape an Azure IoT Hub Device Provisioning Service (DPS)
+via the `DeviceProvisioningService` resource type.
+
+The following fields need to be provided:
+
+- `deviceProvisioningServiceName` - The name of the Azure IoT Hub Device Provisioning Service (DPS)
+
+All supported metrics are documented in the official [Azure Monitor documentation](https://docs.microsoft.com/en-us/azure/azure-monitor/platform/metrics-supported#microsoftdevicesprovisioningservices).
+
+Example:
+
+```yaml
+name: azure_dps_attestation_attempts
+description: "The number of device attestations attempted"
+resourceType: DeviceProvisioningService
+azureMetricConfiguration:
+ metricName: AttestationAttempts
+ aggregation:
+ type: Total
+resources:
+- deviceProvisioningServiceName: promitor-1
+- deviceProvisioningServiceName: promitor-2
+```
+
+
+[← back to metrics declarations](/configuration/v1.x/metrics)
+[← back to introduction](/)
+
diff --git a/src/Promitor.Agents.Scraper/Validation/Factories/MetricValidatorFactory.cs b/src/Promitor.Agents.Scraper/Validation/Factories/MetricValidatorFactory.cs
index 700397e12..ae9e81752 100644
--- a/src/Promitor.Agents.Scraper/Validation/Factories/MetricValidatorFactory.cs
+++ b/src/Promitor.Agents.Scraper/Validation/Factories/MetricValidatorFactory.cs
@@ -55,6 +55,8 @@ internal static IMetricValidator GetValidatorFor(ResourceType resourceType)
return new SqlServerMetricValidator();
case ResourceType.IoTHub:
return new IoTHubMetricValidator();
+ case ResourceType.DeviceProvisioningService:
+ return new DeviceProvisioningServiceMetricValidator();
}
throw new ArgumentOutOfRangeException(nameof(resourceType), $"No validation rules are defined for metric type '{resourceType}'");
diff --git a/src/Promitor.Agents.Scraper/Validation/MetricDefinitions/ResourceTypes/DeviceProvisioningServiceMetricValidator.cs b/src/Promitor.Agents.Scraper/Validation/MetricDefinitions/ResourceTypes/DeviceProvisioningServiceMetricValidator.cs
new file mode 100644
index 000000000..7d8980122
--- /dev/null
+++ b/src/Promitor.Agents.Scraper/Validation/MetricDefinitions/ResourceTypes/DeviceProvisioningServiceMetricValidator.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.Linq;
+using GuardNet;
+using Promitor.Core.Scraping.Configuration.Model.Metrics;
+using Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes;
+using Promitor.Agents.Scraper.Validation.MetricDefinitions.Interfaces;
+
+namespace Promitor.Agents.Scraper.Validation.MetricDefinitions.ResourceTypes
+{
+ internal class DeviceProvisioningServiceMetricValidator : IMetricValidator
+ {
+ public IEnumerable Validate(MetricDefinition metricDefinition)
+ {
+ Guard.NotNull(metricDefinition, nameof(metricDefinition));
+
+ foreach (var resourceDefinition in metricDefinition.Resources.Cast())
+ {
+ if (string.IsNullOrWhiteSpace(resourceDefinition.DeviceProvisioningServiceName))
+ {
+ yield return "No Azure IoT Hub Device Provisioning Service (DPS) name is configured";
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/DeviceProvisioningServiceResourceDefinition.cs b/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/DeviceProvisioningServiceResourceDefinition.cs
new file mode 100644
index 000000000..0ac7d80d9
--- /dev/null
+++ b/src/Promitor.Core.Scraping/Configuration/Model/Metrics/ResourceTypes/DeviceProvisioningServiceResourceDefinition.cs
@@ -0,0 +1,16 @@
+namespace Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes
+{
+ public class DeviceProvisioningServiceResourceDefinition : AzureResourceDefinition
+ {
+ public DeviceProvisioningServiceResourceDefinition(string subscriptionId, string resourceGroupName, string deviceProvisioningServiceName)
+ : base(ResourceType.DeviceProvisioningService, subscriptionId, resourceGroupName)
+ {
+ DeviceProvisioningServiceName = deviceProvisioningServiceName;
+ }
+
+ public string DeviceProvisioningServiceName { get; }
+
+ ///
+ public override string GetResourceName() => DeviceProvisioningServiceName;
+ }
+}
\ No newline at end of file
diff --git a/src/Promitor.Core.Scraping/Configuration/Model/ResourceType.cs b/src/Promitor.Core.Scraping/Configuration/Model/ResourceType.cs
index cd6baa702..c0f06a898 100644
--- a/src/Promitor.Core.Scraping/Configuration/Model/ResourceType.cs
+++ b/src/Promitor.Core.Scraping/Configuration/Model/ResourceType.cs
@@ -25,5 +25,6 @@ public enum ResourceType
StorageAccount = 20,
ApiManagement = 21,
IoTHub = 22,
+ DeviceProvisioningService = 23
}
}
\ No newline at end of file
diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Core/AzureResourceDeserializerFactory.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Core/AzureResourceDeserializerFactory.cs
index 2d421aa7c..076271d33 100644
--- a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Core/AzureResourceDeserializerFactory.cs
+++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Core/AzureResourceDeserializerFactory.cs
@@ -87,6 +87,9 @@ public IDeserializer GetDeserializerFor(ResourceType
case ResourceType.IoTHub:
var iotHubLogger = _loggerFactory.CreateLogger();
return new IoTHubDeserializer(iotHubLogger);
+ case ResourceType.DeviceProvisioningService:
+ var deviceProvisioningServiceLogger = _loggerFactory.CreateLogger();
+ return new DeviceProvisioningServiceDeserializer(deviceProvisioningServiceLogger);
default:
throw new ArgumentOutOfRangeException($"Resource Type {resourceType} not supported.");
}
diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Mapping/V1MappingProfile.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Mapping/V1MappingProfile.cs
index 1982a0061..0ab39fddd 100644
--- a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Mapping/V1MappingProfile.cs
+++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Mapping/V1MappingProfile.cs
@@ -44,6 +44,7 @@ public V1MappingProfile()
CreateMap();
CreateMap();
CreateMap();
+ CreateMap();
CreateMap();
@@ -72,7 +73,8 @@ public V1MappingProfile()
.Include()
.Include()
.Include()
- .Include();
+ .Include()
+ .Include();
}
}
}
diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/DeviceProvisioningServiceResourceV1.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/DeviceProvisioningServiceResourceV1.cs
new file mode 100644
index 000000000..b9f169bd7
--- /dev/null
+++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Model/ResourceTypes/DeviceProvisioningServiceResourceV1.cs
@@ -0,0 +1,13 @@
+namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes
+{
+ ///
+ /// Contains the configuration required to scrape an Azure IoT Hub Device Provisioning Service (DPS).
+ ///
+ public class DeviceProvisioningServiceResourceV1 : AzureResourceDefinitionV1
+ {
+ ///
+ /// The name of the Azure IoT Hub Device Provisioning Service (DPS) to get metrics for.
+ ///
+ public string DeviceProvisioningServiceName { get; set; }
+ }
+}
diff --git a/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/DeviceProvisioningServiceDeserializer.cs b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/DeviceProvisioningServiceDeserializer.cs
new file mode 100644
index 000000000..e84de8861
--- /dev/null
+++ b/src/Promitor.Core.Scraping/Configuration/Serialization/v1/Providers/DeviceProvisioningServiceDeserializer.cs
@@ -0,0 +1,13 @@
+using Microsoft.Extensions.Logging;
+using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes;
+
+namespace Promitor.Core.Scraping.Configuration.Serialization.v1.Providers
+{
+ public class DeviceProvisioningServiceDeserializer : ResourceDeserializer
+ {
+ public DeviceProvisioningServiceDeserializer(ILogger logger) : base(logger)
+ {
+ MapRequired(resource => resource.DeviceProvisioningServiceName);
+ }
+ }
+}
diff --git a/src/Promitor.Core.Scraping/DeviceProvisioningServiceScraper.cs b/src/Promitor.Core.Scraping/DeviceProvisioningServiceScraper.cs
new file mode 100644
index 000000000..dfdea4b27
--- /dev/null
+++ b/src/Promitor.Core.Scraping/DeviceProvisioningServiceScraper.cs
@@ -0,0 +1,26 @@
+using Promitor.Core.Scraping.Configuration.Model.Metrics;
+using Promitor.Core.Scraping.Configuration.Model.Metrics.ResourceTypes;
+
+// ReSharper disable All
+
+namespace Promitor.Core.Scraping
+{
+ ///
+ /// Scrapes an Azure IoT Hub Device Provisioning Service (DPS)
+ ///
+ public class DeviceProvisioningServiceScraper : AzureMonitorScraper
+ {
+ private const string ResourceUriTemplate = "subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Devices/provisioningServices/{2}";
+
+ public DeviceProvisioningServiceScraper(ScraperConfiguration scraperConfiguration) :
+ base(scraperConfiguration)
+ {
+ }
+
+ ///
+ protected override string BuildResourceUri(string subscriptionId, ScrapeDefinition scrapeDefinition, DeviceProvisioningServiceResourceDefinition resource)
+ {
+ return string.Format(ResourceUriTemplate, subscriptionId, scrapeDefinition.ResourceGroupName, resource.DeviceProvisioningServiceName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs b/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs
index e8e07b3e6..57068f948 100644
--- a/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs
+++ b/src/Promitor.Core.Scraping/Factories/MetricScraperFactory.cs
@@ -79,6 +79,8 @@ public IScraper CreateScraper(ResourceType metricDefin
return new FileStorageScraper(scraperConfiguration);
case ResourceType.IoTHub:
return new IoTHubScraper(scraperConfiguration);
+ case ResourceType.DeviceProvisioningService:
+ return new DeviceProvisioningServiceScraper(scraperConfiguration);
default:
throw new ArgumentOutOfRangeException();
}
diff --git a/src/Promitor.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs b/src/Promitor.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs
index 97d891bd7..21cc1af85 100644
--- a/src/Promitor.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs
+++ b/src/Promitor.Tests.Unit/Builders/Metrics/v1/MetricsDeclarationBuilder.cs
@@ -603,5 +603,27 @@ public MetricsDeclarationBuilder WithSqlManagedInstanceMetric(
return this;
}
+
+ public MetricsDeclarationBuilder WithDeviceProvisioningServiceMetric(string metricName = "promitor-dps", string metricDescription = "Description for a metric", string deviceProvisioningServiceName = "promitor-dps", string azureMetricName = "AttestationAttempts")
+ {
+ var azureMetricConfiguration = CreateAzureMetricConfiguration(azureMetricName);
+ var resource = new DeviceProvisioningServiceResourceV1
+ {
+ DeviceProvisioningServiceName = deviceProvisioningServiceName
+ };
+
+ var metric = new MetricDefinitionV1
+ {
+ Name = metricName,
+ Description = metricDescription,
+ AzureMetricConfiguration = azureMetricConfiguration,
+ Resources = new List { resource },
+ ResourceType = ResourceType.DeviceProvisioningService
+ };
+
+ _metrics.Add(metric);
+
+ return this;
+ }
}
}
\ No newline at end of file
diff --git a/src/Promitor.Tests.Unit/Serialization/v1/Providers/DeviceProvisioningServiceDeserializerTests.cs b/src/Promitor.Tests.Unit/Serialization/v1/Providers/DeviceProvisioningServiceDeserializerTests.cs
new file mode 100644
index 000000000..db77e6c56
--- /dev/null
+++ b/src/Promitor.Tests.Unit/Serialization/v1/Providers/DeviceProvisioningServiceDeserializerTests.cs
@@ -0,0 +1,45 @@
+using Promitor.Core.Scraping.Configuration.Serialization;
+using Promitor.Core.Scraping.Configuration.Serialization.v1.Model;
+using Promitor.Core.Scraping.Configuration.Serialization.v1.Model.ResourceTypes;
+using Promitor.Core.Scraping.Configuration.Serialization.v1.Providers;
+using System.ComponentModel;
+using Xunit;
+
+namespace Promitor.Tests.Unit.Serialization.v1.Providers
+{
+ [Category("Unit")]
+ public class DeviceProvisioningServiceDeserializerTests : ResourceDeserializerTest
+ {
+ private readonly DeviceProvisioningServiceDeserializer _deserializer;
+
+ public DeviceProvisioningServiceDeserializerTests()
+ {
+ _deserializer = new DeviceProvisioningServiceDeserializer(Logger);
+ }
+
+ [Fact]
+ public void Deserialize_DeviceProvisioningServiceNameSupplied_SetsDeviceProvisioningServiceName()
+ {
+ const string deviceProvisioningServiceName = "promitor-dps";
+ YamlAssert.PropertySet(
+ _deserializer,
+ $"deviceProvisioningServiceName: {deviceProvisioningServiceName}",
+ deviceProvisioningServiceName,
+ r => r.DeviceProvisioningServiceName);
+ }
+
+ [Fact]
+ public void Deserialize_DeviceProvisioningServiceNameNotSupplied_Null()
+ {
+ YamlAssert.PropertyNull(
+ _deserializer,
+ "resourceGroupName: promitor-group",
+ r => r.DeviceProvisioningServiceName);
+ }
+
+ protected override IDeserializer CreateDeserializer()
+ {
+ return new DeviceProvisioningServiceDeserializer(Logger);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Promitor.Tests.Unit/Validation/Metrics/ResourceTypes/DeviceProvisioningServiceMetricsDeclarationValidationStepsTests.cs b/src/Promitor.Tests.Unit/Validation/Metrics/ResourceTypes/DeviceProvisioningServiceMetricsDeclarationValidationStepsTests.cs
new file mode 100644
index 000000000..57eff0d94
--- /dev/null
+++ b/src/Promitor.Tests.Unit/Validation/Metrics/ResourceTypes/DeviceProvisioningServiceMetricsDeclarationValidationStepsTests.cs
@@ -0,0 +1,97 @@
+using System.ComponentModel;
+using Promitor.Agents.Scraper.Validation.Steps;
+using Promitor.Tests.Unit.Builders.Metrics.v1;
+using Promitor.Tests.Unit.Stubs;
+using Xunit;
+
+namespace Promitor.Tests.Unit.Validation.Metrics.ResourceTypes
+{
+ [Category("Unit")]
+ public class DeviceProvisioningServiceMetricsDeclarationValidationStepsTests : MetricsDeclarationValidationStepsTests
+ {
+ [Fact]
+ public void DeviceProvisioningServiceMetricsDeclaration_DeclarationWithoutAzureMetricName_Fails()
+ {
+ // Arrange
+ var rawDeclaration = MetricsDeclarationBuilder.WithMetadata()
+ .WithDeviceProvisioningServiceMetric(azureMetricName: string.Empty)
+ .Build(Mapper);
+ var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, Mapper);
+
+ // Act
+ var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider);
+ var validationResult = scrapingScheduleValidationStep.Run();
+
+ // Assert
+ Assert.False(validationResult.IsSuccessful, "Validation is successful");
+ }
+
+ [Fact]
+ public void DeviceProvisioningServiceMetricsDeclaration_DeclarationWithoutMetricDescription_Succeeded()
+ {
+ // Arrange
+ var rawDeclaration = MetricsDeclarationBuilder.WithMetadata()
+ .WithDeviceProvisioningServiceMetric(metricDescription: string.Empty)
+ .Build(Mapper);
+ var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, Mapper);
+
+ // Act
+ var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider);
+ var validationResult = scrapingScheduleValidationStep.Run();
+
+ // Assert
+ Assert.True(validationResult.IsSuccessful, "Validation was not successful");
+ }
+
+ [Fact]
+ public void DeviceProvisioningServiceMetricsDeclaration_DeclarationWithoutMetricName_Fails()
+ {
+ // Arrange
+ var rawDeclaration = MetricsDeclarationBuilder.WithMetadata()
+ .WithDeviceProvisioningServiceMetric(string.Empty)
+ .Build(Mapper);
+ var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, Mapper);
+
+ // Act
+ var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider);
+ var validationResult = scrapingScheduleValidationStep.Run();
+
+ // Assert
+ Assert.False(validationResult.IsSuccessful, "Validation is successful");
+ }
+
+ [Fact]
+ public void DeviceProvisioningServiceMetricsDeclaration_DeclarationWithoutDeviceProvisioningServiceName_Fails()
+ {
+ // Arrange
+ var rawDeclaration = MetricsDeclarationBuilder.WithMetadata()
+ .WithDeviceProvisioningServiceMetric(deviceProvisioningServiceName: string.Empty)
+ .Build(Mapper);
+ var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawDeclaration, Mapper);
+
+ // Act
+ var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider);
+ var validationResult = scrapingScheduleValidationStep.Run();
+
+ // Assert
+ Assert.False(validationResult.IsSuccessful, "Validation is successful");
+ }
+
+ [Fact]
+ public void DeviceProvisioningServiceMetricsDeclaration_ValidDeclaration_Succeeds()
+ {
+ // Arrange
+ var rawMetricsDeclaration = MetricsDeclarationBuilder.WithMetadata()
+ .WithDeviceProvisioningServiceMetric()
+ .Build(Mapper);
+ var metricsDeclarationProvider = new MetricsDeclarationProviderStub(rawMetricsDeclaration, Mapper);
+
+ // Act
+ var scrapingScheduleValidationStep = new MetricsDeclarationValidationStep(metricsDeclarationProvider);
+ var validationResult = scrapingScheduleValidationStep.Run();
+
+ // Assert
+ Assert.True(validationResult.IsSuccessful, "Validation was not successful");
+ }
+ }
+}