This commit is contained in:
Spyros Garyfallos 2018-06-03 12:12:02 -07:00
Родитель f4e393c8d2
Коммит ea95640c2a
51 изменённых файлов: 376 добавлений и 179 удалений

Просмотреть файл

@ -1 +1 @@
DOCKER_REGISTRY=typeedge.azurecr.io/
DOCKER_REGISTRY=typeedge.azurecr.io/

Просмотреть файл

@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.26" Condition="'$(Configuration)|$(Platform)'!='TemplateDevelopment|AnyCPU'" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.48" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -7,7 +7,7 @@ namespace Modules
{
public static async Task Main(string[] args)
{
await Startup.DockerEntryPoint(args);
await Startup.DockerEntryPoint(args);
}
}
}

Просмотреть файл

@ -1,10 +0,0 @@
{
"profiles": {
"NormalizeTemperatureModule": {
"commandName": "Project",
"environmentVariables": {
"ModuleName": "normalizetemperaturemodule"
}
}
}
}

Просмотреть файл

@ -1,10 +0,0 @@
{
"profiles": {
"TemperatureModule": {
"commandName": "Project",
"environmentVariables": {
"ModuleName": "Temperature"
}
}
}
}

Просмотреть файл

@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.26" Condition="'$(Configuration)|$(Platform)'!='TemplateDevelopment|AnyCPU'" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.48" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -4,7 +4,7 @@ using Microsoft.Azure.IoT.TypeEdge.Host;
using Microsoft.Extensions.Configuration;
using Modules;
using ThermostatApplication.Modules;
using Microsoft.Azure.IoT.TypeEdge.Host.DovEnv;
using Microsoft.Azure.IoT.TypeEdge.DovEnv;
namespace ThermostatApplication
{

Просмотреть файл

@ -18,7 +18,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Host" Version="0.1.26" Condition="'$(Configuration)|$(Platform)'!='TemplateDevelopment|AnyCPU'" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Host" Version="0.1.48" />
<PackageReference Include="rocksdb-native-arm" Version="5.4.6" />
<PackageReference Include="RocksDbNative" Version="5.4.6.10" />
</ItemGroup>

Просмотреть файл

@ -2,6 +2,6 @@
"TypeEdgeHost": {
"IotHubConnectionString": "HostName=iotedgedev-iothub-7389d7.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=UqshG8knBj9ZG5J8tM9S7rTSQI4Ho1L5eWPz3Subkbo=",
"DeviceId": "type-edge-dev8",
"PrintDeploymentJson": "true"
"PrintDeploymentJson": "true"
}
}

Просмотреть файл

@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Proxy" Version="0.1.26" Condition="'$(Configuration)|$(Platform)'!='TemplateDevelopment|AnyCPU'" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Proxy" Version="0.1.48" />
</ItemGroup>

Просмотреть файл

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.26" Condition="'$(Configuration)|$(Platform)'!='TemplateDevelopment|AnyCPU'" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.48" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -0,0 +1,49 @@
@echo off
IF "%1"=="" GOTO HAVE_USERNAME
IF "%2"=="" GOTO HAVE_PASSWORD
if exist nuget.exe (
echo nuget.exe found
) else (
echo downloading nuget.exe from https://www.nuget.org/nuget.exe
Powershell.exe wget -outf nuget.exe https://nuget.org/nuget.exe
if not exist .\nuget.exe (
echo Error: nuget does not exist.
exit /b 1
)
)
echo Adding the private nuget packages feed "private-typeedge-feed"
nuget.exe sources Add -Name "private-typeedge-feed" -Source "https://msblox-03.pkgs.visualstudio.com/_packaging/private-nuget-feed/nuget/v3/index.json" -StorePasswordInClearText -ConfigFile NuGet.Config -UserName "%1" -Password "%2"
if errorlevel 1 (
echo Failed, trying to remove an existing record first ..
echo Removing the private "private-typeedge-feed" ..
nuget.exe sources Remove -Name "private-typeedge-feed"
echo Adding the private nuget packages feed "private-typeedge-feed"
nuget.exe sources Add -Name "private-typeedge-feed" -Source "https://msblox-03.pkgs.visualstudio.com/_packaging/private-nuget-feed/nuget/v3/index.json" -StorePasswordInClearText -ConfigFile NuGet.Config -UserName "%1" -Password "%2"
if errorlevel 0 (
echo Success!
)
)
if exist nuget.exe (
del nuget.exe /q
)
exit /b 0
:HAVE_USERNAME
echo Your Git credentials username is required
exit /b 1
:HAVE_PASSWORD
echo Your Git credentials password is required
exit /b 1
:exit
echo exiting..

Просмотреть файл

@ -6,6 +6,7 @@
<ProjectGuid>1373dbdc-dc13-429e-8c17-2e4d85cf42ba</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<None Include=".env" />
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>

Просмотреть файл

@ -1,22 +1,29 @@
version: '3.4'
volumes:
env:
services:
normalizetemperaturemodule:
image: ${DOCKER_REGISTRY}normalizetemperature
build:
context: .
dockerfile: Modules/NormalizeTemperatureModule/Dockerfile
temperaturemodule:
image: ${DOCKER_REGISTRY}temperature
build:
context: .
dockerfile: Modules/TemperatureModule/Dockerfile
thermostat.emulator:
image: ${DOCKER_REGISTRY}thermostatemulator
build:
context: .
dockerfile: Thermostat.Emulator/Dockerfile
volumes:
- env:/env
normalizetemperaturemodule:
image: ${DOCKER_REGISTRY}normalizetemperaturemodule
build:
context: .
dockerfile: Modules/NormalizeTemperatureModule/Dockerfile
volumes:
- env:/env
temperaturemodule:
image: ${DOCKER_REGISTRY}temperaturemodule
build:
context: .
dockerfile: Modules/TemperatureModule/Dockerfile
volumes:
- env:/env

Двоичные данные
Example/nuget.exe

Двоичный файл не отображается.

Просмотреть файл

@ -12,18 +12,31 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{F2FF1756-9AAF-411B-8B54-3829EAC21515}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TemperatureModule", "Example\Modules\TemperatureModule\TemperatureModule.csproj", "{48302C68-971D-414F-A29D-742F79415D74}"
ProjectSection(ProjectDependencies) = postProject
{54587D56-7C22-4084-9321-84D57C2F6ECB} = {54587D56-7C22-4084-9321-84D57C2F6ECB}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NormalizeTemperatureModule", "Example\Modules\NormalizeTemperatureModule\NormalizeTemperatureModule.csproj", "{30A335A6-47C9-435A-BA23-50988EAF2C33}"
ProjectSection(ProjectDependencies) = postProject
{54587D56-7C22-4084-9321-84D57C2F6ECB} = {54587D56-7C22-4084-9321-84D57C2F6ECB}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thermostat.Shared", "Example\Thermostat.Shared\Thermostat.Shared.csproj", "{45F97F43-7C75-49B8-8525-7F0E9C7B9FB7}"
ProjectSection(ProjectDependencies) = postProject
{54587D56-7C22-4084-9321-84D57C2F6ECB} = {54587D56-7C22-4084-9321-84D57C2F6ECB}
{E738845B-4D98-4CB9-BDA4-F83193FE81B5} = {E738845B-4D98-4CB9-BDA4-F83193FE81B5}
{6F13F88C-B271-4DA6-9A35-5FD7FEADD3A3} = {6F13F88C-B271-4DA6-9A35-5FD7FEADD3A3}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thermostat.Emulator", "Example\Thermostat.Emulator\Thermostat.Emulator.csproj", "{2076DDEF-8EE3-4BC1-BF79-1C1F62A8719D}"
ProjectSection(ProjectDependencies) = postProject
{54587D56-7C22-4084-9321-84D57C2F6ECB} = {54587D56-7C22-4084-9321-84D57C2F6ECB}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thermostat.ServiceApp", "Example\Thermostat.ServiceApp\Thermostat.ServiceApp.csproj", "{286CDAA1-5410-4322-AC1C-F35A83CDA63D}"
ProjectSection(ProjectDependencies) = postProject
{54587D56-7C22-4084-9321-84D57C2F6ECB} = {54587D56-7C22-4084-9321-84D57C2F6ECB}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.IoT.TypeEdge.Proxy", "Microsoft.Azure.IoT.TypeEdge.Proxy\Microsoft.Azure.IoT.TypeEdge.Proxy.csproj", "{E738845B-4D98-4CB9-BDA4-F83193FE81B5}"
EndProject

Просмотреть файл

@ -2,14 +2,14 @@
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>Microsoft.Azure.IoT.TypeEdge.Host</id>
<version>0.1.26</version>
<version>0.1.48</version>
<authors>paloukari</authors>
<owners>paloukari</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Package Description</description>
<dependencies>
<group targetFramework=".NETCoreApp2.1">
<dependency id="Microsoft.Azure.IoT.TypeEdge" version="0.1.26" exclude="Build,Analyzers" />
<dependency id="Microsoft.Azure.IoT.TypeEdge" version="0.1.48" exclude="Build,Analyzers" />
<dependency id="Microsoft.Azure.Devices.Edge.Agent.Core" version="1.0.0" exclude="Build,Analyzers" />
<dependency id="Microsoft.Azure.Devices.Edge.Hub.Service" version="1.0.0" exclude="Build,Analyzers" />
</group>

Просмотреть файл

@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Castle.DynamicProxy;
using Microsoft.Azure.Devices;
using Microsoft.Azure.Devices;
using Microsoft.Azure.Devices.Common.Exceptions;
using Microsoft.Azure.Devices.Edge.Agent.Core;
using Microsoft.Azure.Devices.Edge.Storage;
@ -34,6 +34,7 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host
private readonly EdgeHub _hub;
private ModuleCollection _modules;
private readonly TypeEdgeHostOptions _options;
private readonly bool _inContainer;
public TypeEdgeHost(IConfigurationRoot configuration)
@ -53,6 +54,8 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host
_hub = new EdgeHub();
Upstream = new Upstream<JsonMessage>(_hub);
_inContainer = File.Exists(@"/.dockerenv");
}
public Upstream<JsonMessage> Upstream { get; set; }
@ -74,7 +77,8 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host
public void Build()
{
//setup the container
CleanUp();
BuildContainer();
_modules = CreateModules();
@ -86,6 +90,14 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host
BuildHub(deviceSasKey);
}
private void CleanUp()
{
if (!_inContainer || !Directory.Exists(TypeEdge.Constants.ComposeConfigurationPath))
return;
foreach (var file in Directory.EnumerateFiles(TypeEdge.Constants.ComposeConfigurationPath))
File.Delete(file);
}
public async Task RunAsync()
{
@ -93,9 +105,11 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host
{
_hub.RunAsync()
};
//start all modules
foreach (var module in _modules)
tasks.Add(module.InternalRunAsync());
if (!_inContainer)
foreach (var module in _modules)
tasks.Add(module.InternalRunAsync());
await Task.WhenAll(tasks.ToArray());
}
@ -164,22 +178,49 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host
private void ConfigureModules()
{
var certificatePath = @"Certificates/edge-device-ca/cert/edge-device-ca-root.cert.pem";
var currentLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
Environment.SetEnvironmentVariable(Devices.Edge.Agent.Core.Constants.EdgeModuleCaCertificateFileKey,
Path.Combine(currentLocation,
certificatePath));
foreach (var module in _modules)
{
var moduleConnectionString =
GetModuleConnectionStringAsync(_options.IotHubConnectionString, _options.DeviceId, module.Name)
.Result;
Environment.SetEnvironmentVariable(Devices.Edge.Agent.Core.Constants.EdgeHubConnectionStringKey,
moduleConnectionString);
Environment.SetEnvironmentVariable(Devices.Edge.Agent.Core.Constants.EdgeModuleCaCertificateFileKey,
Path.Combine(currentLocation, @"Certificates/edge-device-ca/cert/edge-device-ca-root.cert.pem"));
var moduleConfiguration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
if (_inContainer)
{
System.Console.WriteLine("Emulator running in docker-compose mode.");
module.InternalConfigure(moduleConfiguration);
const string envPath = TypeEdge.Constants.ComposeConfigurationPath;
var path = Path.Combine(envPath, $"{module.Name}.env");
var certPath = Path.Combine(envPath, certificatePath);
var dotenvData = $"{Devices.Edge.Agent.Core.Constants.EdgeHubConnectionStringKey}={moduleConnectionString}";
dotenvData += $"{Environment.NewLine}{Devices.Edge.Agent.Core.Constants.EdgeModuleCaCertificateFileKey}={certPath}";
if (!Directory.Exists(Path.GetDirectoryName(certPath)))
Directory.CreateDirectory(Path.GetDirectoryName(certPath));
if (!File.Exists(certPath))
File.Copy(Path.Combine(currentLocation, certificatePath), certPath);
File.WriteAllText(path, dotenvData);
}
else
{
Environment.SetEnvironmentVariable(Devices.Edge.Agent.Core.Constants.EdgeHubConnectionStringKey,
moduleConnectionString);
var moduleConfiguration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
module.InternalConfigure(moduleConfiguration);
}
}
}
@ -210,7 +251,7 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host
try
{
var device = await registryManager.AddDeviceAsync(
new Device(_options.DeviceId) {Capabilities = new DeviceCapabilities {IotEdge = true}});
new Device(_options.DeviceId) { Capabilities = new DeviceCapabilities { IotEdge = true } });
sasKey = device.Authentication.SymmetricKey.PrimaryKey;
}
catch (DeviceAlreadyExistsException)
@ -231,7 +272,7 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host
var modulesConfig =
configurationContent.ModuleContent["$edgeAgent"].TargetContent["modules"] as JObject;
var dockerRegistry = Environment.GetEnvironmentVariable("DOCKER_REGISTRY") ?? "";
var dockerRegistry = _configuration.GetValue<string>("DOCKER_REGISTRY") ?? "";
foreach (var module in _modules)
{
@ -265,8 +306,8 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host
var routes = new Dictionary<string, string>();
foreach (var module in _modules)
foreach (var route in module.Routes)
routes[$"route{routes.Count}"] = route;
foreach (var route in module.Routes)
routes[$"route{routes.Count}"] = route;
foreach (var route in _hub.Routes) routes[$"route{routes.Count}"] = route;

Просмотреть файл

@ -1,4 +1,6 @@
using Serilog.Events;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Serilog.Events;
namespace Microsoft.Azure.IoT.TypeEdge.Host
{
@ -7,7 +9,9 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host
public string IotHubConnectionString { get; set; }
public string DeviceId { get; set; }
public LogEventLevel? RuntimeLogLevel { get; set; }
public bool? PrintDeploymentJson { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public RunningEnvironment? Environment { get; set; }
}
}

Просмотреть файл

@ -2,7 +2,7 @@
<PropertyGroup>
<PackageId>Microsoft.Azure.IoT.TypeEdge.Proxy</PackageId>
<Version>0.1.26</Version>
<Version>0.1.48</Version>
<Authors>paloukari</Authors>
<Company>Microsoft</Company>

Просмотреть файл

@ -4,6 +4,7 @@
{
public const string ConfigFileName = "appsettings_compose.json";
public const string EdgeHubConnectionStringKey = "EdgeHubConnectionString";
public const string ModuleNameConfigName = "ModuleName";
public const string ModuleNameConfigName = "moduleName";
public const string ComposeConfigurationPath = "/env";
}
}

Просмотреть файл

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
namespace Microsoft.Azure.IoT.TypeEdge.Host.DovEnv
namespace Microsoft.Azure.IoT.TypeEdge.DovEnv
{
public class Dotenv
{

Просмотреть файл

@ -1,7 +1,7 @@
using System.IO;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Azure.IoT.TypeEdge.Host.DovEnv
namespace Microsoft.Azure.IoT.TypeEdge.DovEnv
{
public class DotenvConfigurationProvider : FileConfigurationProvider
{

Просмотреть файл

@ -1,6 +1,6 @@
using Microsoft.Extensions.Configuration;
namespace Microsoft.Azure.IoT.TypeEdge.Host.DovEnv
namespace Microsoft.Azure.IoT.TypeEdge.DovEnv
{
public class DotenvConfigurationSource : FileConfigurationSource
{

Просмотреть файл

@ -3,8 +3,9 @@ using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
using PhysicalFileProvider = Microsoft.Azure.IoT.TypeEdge.DovEnv.FileProvider.PhysicalFileProvider;
namespace Microsoft.Azure.IoT.TypeEdge.Host.DovEnv
namespace Microsoft.Azure.IoT.TypeEdge.DovEnv
{
public static class DotenvExtension
{
@ -67,17 +68,18 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host.DovEnv
//--
// The below is still needed for .NET Core 1.x
//--
var fileExists = false;
foreach (var basePath in basePaths)
{
var testPath = string.Join("/", new string[] { basePath, path });
if (File.Exists(testPath))
var fileExists = path.StartsWith('/') && File.Exists(path);
if (!fileExists)
foreach (var basePath in basePaths)
{
var testPath = string.Join("/", new string[] { basePath, path });
Console.WriteLine($"DotEnv: checking for {testPath}");
if (!File.Exists(testPath))
continue;
fileExists = true;
path = testPath;
break;
}
}
if (!fileExists && !optional)
{
throw new Exception($"The .env configuration file '{path}' was not found");
@ -86,7 +88,7 @@ namespace Microsoft.Azure.IoT.TypeEdge.Host.DovEnv
{
// Real PhysicalFileProvider has a bug that don't allow dot files:
// https://github.com/aspnet/FileSystem/issues/232
provider = new FileProvider.PhysicalFileProvider(Path.GetDirectoryName(path));
provider = new PhysicalFileProvider(Path.GetDirectoryName(path));
path = Path.GetFileName(path);
}
}

Просмотреть файл

@ -4,7 +4,7 @@ using System.Diagnostics;
using System.Threading;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Azure.IoT.TypeEdge.Host.DovEnv.FileProvider
namespace Microsoft.Azure.IoT.TypeEdge.DovEnv.FileProvider
{
public class CompositeChangeToken : IChangeToken
{

Просмотреть файл

@ -5,8 +5,9 @@ using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Internal;
using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.Primitives;
using PhysicalFilesWatcher = Microsoft.Azure.IoT.TypeEdge.DovEnv.FileProvider.PhysicalFilesWatcher;
namespace Microsoft.Azure.IoT.TypeEdge.Host.DovEnv.FileProvider
namespace Microsoft.Azure.IoT.TypeEdge.DovEnv.FileProvider
{
public class PhysicalFileProvider : IFileProvider, IDisposable
{

Просмотреть файл

@ -8,7 +8,7 @@ using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Azure.IoT.TypeEdge.Host.DovEnv.FileProvider
namespace Microsoft.Azure.IoT.TypeEdge.DovEnv.FileProvider
{
public class PhysicalFilesWatcher : IDisposable
{

Просмотреть файл

@ -2,7 +2,7 @@
<PropertyGroup>
<PackageId>Microsoft.Azure.IoT.TypeEdge</PackageId>
<Version>0.1.26</Version>
<Version>0.1.48</Version>
<Authors>paloukari</Authors>
<Company>Microsoft</Company>
<PackageOutputPath>../../TypeEdgeNuGets</PackageOutputPath>

Просмотреть файл

@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Microsoft.Azure.Devices.Client;
using Microsoft.Azure.Devices.Client.Transport.Mqtt;
using Microsoft.Azure.Devices.Shared;
using Microsoft.Azure.IoT.TypeEdge.Attributes;
using Microsoft.Azure.IoT.TypeEdge.Enums;
using Microsoft.Azure.IoT.TypeEdge.Modules.Endpoints;
using Microsoft.Azure.IoT.TypeEdge.Modules.Enums;
@ -71,7 +70,6 @@ namespace Microsoft.Azure.IoT.TypeEdge.Modules
internal virtual async Task<T> GetTwinAsync<T>(string name)
where T : IModuleTwin, new()
{
var typeTwin = Activator.CreateInstance<T>();
typeTwin.SetTwin(name, await _ioTHubModuleClient.GetTwinAsync());
@ -151,7 +149,11 @@ namespace Microsoft.Azure.IoT.TypeEdge.Modules
internal CreationResult InternalConfigure(IConfigurationRoot configuration)
{
Console.WriteLine($"{Name}:InternalConfigure called");
_connectionString = configuration.GetValue<string>(Constants.EdgeHubConnectionStringKey);
_connectionString = configuration.GetValue<string>($"{Constants.EdgeHubConnectionStringKey}");
if (string.IsNullOrEmpty(_connectionString))
throw new ArgumentException($"Missing {Constants.EdgeHubConnectionStringKey} in configuration for {Name}");
// Cert verification is not yet fully functional when using Windows OS for the container
var bypassCertVerification = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

Просмотреть файл

@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
@ -8,10 +9,12 @@ using Autofac;
using Autofac.Extensions.DependencyInjection;
using Castle.DynamicProxy;
using Microsoft.Azure.IoT.TypeEdge.Attributes;
using Microsoft.Azure.IoT.TypeEdge.DovEnv;
using Microsoft.Azure.IoT.TypeEdge.Modules;
using Microsoft.Azure.IoT.TypeEdge.Proxy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using static System.String;
namespace Microsoft.Azure.IoT.TypeEdge
{
@ -31,22 +34,65 @@ namespace Microsoft.Azure.IoT.TypeEdge
.AddCommandLine(args)
.Build();
var moduleName = configuration.GetValue<string>(Constants.ModuleNameConfigName);
//todo: throw or warn here?
if (string.IsNullOrEmpty(moduleName))
if (IsNullOrEmpty(moduleName))
{
Console.WriteLine($"WARN:No {Constants.ModuleNameConfigName} in configuration. ");
Console.WriteLine("Exiting...");
return;
//throw new ArgumentException($"No moduleName in arguments");
moduleName = DiscoverModuleName();
if (IsNullOrEmpty(moduleName))
{
Console.WriteLine("Exiting...");
return;
//throw new ArgumentException($"No moduleName in arguments");
}
}
var (moduleType, _) = GetModuleTypes(moduleName);
if (moduleType == null)
throw new ArgumentException($"No module callled {moduleName} in calling assembly");
throw new ArgumentException($"No module called {moduleName} in calling assembly");
var connectionString = configuration.GetValue<string>($"{Constants.EdgeHubConnectionStringKey}");
if (IsNullOrEmpty(connectionString))
{
//check the file system, we are in docker-compose mode
var fileName = Path.Combine(Constants.ComposeConfigurationPath, $"{moduleName}.env");
var remainingSeconds = 30;
while (remainingSeconds-- > 0)
{
if (File.Exists(fileName))
{
configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddCommandLine(args)
.AddDotenvFile(fileName)
.Build();
File.Delete(fileName);
break;
}
var directory = Path.GetDirectoryName(fileName);
foreach (var file in Directory.EnumerateFiles(directory))
{
Console.WriteLine(file);
}
Console.WriteLine($"{moduleName}:{fileName} does not exist. Retrying in 1 sec.");
Thread.Sleep(1000);
}
if (remainingSeconds < 0)
{
Console.WriteLine($"{moduleName}:No {moduleName}.env found.");
Console.WriteLine($"{moduleName}:Exiting...");
return;
}
}
containerBuilder.RegisterType(moduleType);
@ -81,6 +127,20 @@ namespace Microsoft.Azure.IoT.TypeEdge
await WhenCancelled(cts.Token);
}
private static string DiscoverModuleName()
{
var assembly = Assembly.GetEntryAssembly();
var moduleType = assembly.GetTypes().SingleOrDefault(t =>
t.GetInterfaces().SingleOrDefault(i =>
i.GetCustomAttribute(typeof(TypeModuleAttribute), true) != null) != null);
if (moduleType == null)
return null;
var moduleInterfaceType = moduleType.GetProxyInterface();
return moduleInterfaceType.Name.Substring(1).ToLower();
}
public static Task WhenCancelled(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
@ -105,7 +165,7 @@ namespace Microsoft.Azure.IoT.TypeEdge
var moduleType = assembly.GetTypes().SingleOrDefault(t =>
t.GetInterfaces().SingleOrDefault(i =>
i.GetCustomAttribute(typeof(TypeModuleAttribute), true) != null &&
string.Equals(i.Name.Substring(1), moduleName, StringComparison.CurrentCultureIgnoreCase)) != null);
String.Equals(i.Name.Substring(1), moduleName, StringComparison.CurrentCultureIgnoreCase)) != null);
if (moduleType == null)
{

Просмотреть файл

@ -14,6 +14,7 @@
<packageSources>
<add key="RocksDB ARM" value="https://www.myget.org/F/rocksdb-native-arm/api/v3/index.json" />
<add key="https://www.nuget.org/api/v2/" value="https://www.nuget.org/api/v2/" />
<add key="localSource" value="../TypeEdgeNuGets" />
</packageSources>
<disabledPackageSources>
<add key="https://www.nuget.org/api/v2/" value="true" />

170
README.md
Просмотреть файл

@ -1,111 +1,137 @@
# Azure IoT TypeEdge
The **Azure IoT TypeEdge** introduces a strongly typed flavor of the inherently loosely coupled vanilla [Azure IoT Edge](https:/azure.microsoft.com/en-us/services/iot-edge/).
The **Azure IoT TypeEdge** introduces a strongly-typed flavor of the inherently loosely coupled vanilla [Azure IoT Edge](https:/azure.microsoft.com/en-us/services/iot-edge/).
Specifically:
Specifically, **TypeEdge**:
- It removes all configuration burden from an IoT Edge application, because configuration can be now automatically generated by code reflection.
- Introduces compile time types checking across all modules
- It adds the ability to **emulate an IoT Edge device on the development environment**, by running all components in memory, no containers involved
- It simplifies the IoT Edge development down to an single F5 experience
- Removes all configuration burden from an IoT Edge application, because configuration can be now automatically generated.
- Introduces compile-time types checking across all modules
- Adds the ability to **emulate an IoT Edge device in-memory** with no containers involved
- Simplifies the IoT Edge development, down to an single F5 experience
## Initial Setup
## Prerequisites
The minimum requirements to get started with **TypeEdge** are:
- The latest [.NET Core SDK](https://github.com/dotnet/core/blob/master/release-notes/download-archives/2.1.0-download.md) (version 2.1.300). To find your current version, run
`dotnet --version`
- An [Azure IoT Hub](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-create-through-portal)
To be able to publish your application, you will also need:
- [Docker](https://docs.docker.com/engine/installation/)
- An [Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal), or any other secure container registry.
- Temporarily, you'll need your git credentials to login [to the private packages feed.](https://msblox-03.visualstudio.com/csetypeedge)
Make sure you have [Docker](https://docs.docker.com/engine/installation/), the latest [.NET Core SDK](https://github.com/dotnet/core/blob/master/release-notes/download-archives/2.1.0-download.md) (version 2.1.300) and your git credentials to login [here](https://msblox-03.visualstudio.com/csetypeedge)
> Note: To get your git credentials, navigate to the [VSTS repo](https://msblox-03.visualstudio.com/csetypeedge) and under **"clone to your computer"** section, click **"Generate Git Credentials"**.
You will need also an [Azure IoT Hub](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-create-through-portal) and an [Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal), or any other secure container registry.
## QuickStart
Here is the quickest way to get started with **TypeEdge**. In this quick start you will create an IoT Edge application with two modules and run it in the IoT Edge emulator:
## Create a new **TypeEdge** application
Here is the quickest way to get started with **TypeEdge**. In this quick start you will create an IoT Edge application with two modules and run it in the emulator:
1. Install the TypeEdge .NET Core solution template. Just type:
```
dotnet new -i TypeEdge.Application
```
>**Note:** If you already installed the template and you want to **upgrade to a newer template version**, you need to clear the dotnet http and template cache
>**Note:** to **upgrade to a newer template version**, you need to clear the dotnet http and template cache first
```
dotnet nuget locals http-cache --clear
dotnet new --debug:reinit
```
2. Copy the **iothubowner connection string** from your development Azure **IoT Hub**.
1. Copy the **iothubowner** connection string from your Azure **IoT Hub**.
> The **iothubowner** is required because TypeEdge needs to provision a new device with the generated deployment configuration.
1. Create a new IoT TypeEdge application:
> You can choose the TypeEdge application and modules names of this template. In the example below, the application is called **Thermostat**, and the two modules are called **SensorModule** and **PreprocessorModule**. These names will be used as class names, so **Pascal casing** is suggested.
> You can choose the TypeEdge application and modules names. In the example below, the application is called **Thermostat**, and the two modules are called **SensorModule** and **PreprocessorModule**. These names will be used as class names, so **Pascal casing** is suggested.
```
dotnet new typeedgeapp -n Thermostat -m1 SensorModule -m2 PreprocessorModule -cs "YOUR_IOTHUBOWNER_CONNECTION" -cr YOUR_CONTAINER_REGISTRY
```
>Note: a localhost registry will not work in this version.
1. Temporary step:
Navigate inside the root folder to add the private packages source. Then, **add your git credentials to the command below** and run it. This will download nuget.exe and add the private packages source in your solution.
```
cd Thermostat
addPrivateSource.bat USERNAME PASSWORD
```
3. Open in VS Code/Visual Studio 2017 and hit F5:
```
code .
```
..or run it in the command line:
```
dotnet build Thermostat.sln
cd Thermostat.Emulator
dotnet run
```
>Note: a localhost registry is not supported at the moment.
> Note: You should see now the Edge Hub starting up..
![](images/IoTEdge.png) .. and the messages flowing in .. ![](images/messages.png)
## Build and debug the application
1. Navigate in the application folder:
cd Thermostat
1. Open in VS Code/Visual Studio 2017 and hit F5:
- For VS Code run
code .
- For VS 2017 run
Thermostat.sln
- To run the application in the command line (no IDE):
```
dotnet build Thermostat.sln
cd Thermostat.Emulator
dotnet run
```
>Note: In all three cases, your application is being emulated in-memory without any containers involved. This is very useful for quick develop and test iterations.
You should see now the Edge Hub starting up..
![](images/IoTEdge.png)
.. and the messages flowing in ..
![](images/messages.png)
## Containers debugging
If your modules have system dependencies and you want to debug inside the containers, you can leverage the docker support feature of VS 2017. Simply start the docker-compose project from VS 2017 to **debug your application inside the docker containers**.
Alternatively, you can run your application inside the containers in command line:
docker-compose -f docker-compose.yml -f docker-compose.override.yml -f docker-compose.vs.debug.yml up
> Note: To build the docker containers, temporarily you need to [add the private NuGet feed](#feed) first.
![](images/incontainer.png)
**Congratulations!**
You just created your first **TypeEdge** application. Continue reading below to learn how to deploy this application to an IoT Device, or take the time to understand [how it works](#how).
You just created your first **TypeEdge** application. Continue reading to learn how to deploy this application to an IoT Device, or take the time to understand [how it works](#how).
## Publish the Application
##
1. ***Temporary step* <a name="feed">Private packages feed:</a>** To build the containers, you have to add the private packages source first. **Add your git credentials to the private repo** and run the below command. This will download nuget.exe and add the private packages source in your solution.
addPrivateSource.bat USERNAME PASSWORD
1. Now, you can build the container images:
docker-compose build
1. The final step is to push these images to your docker registry. Make sure Docker can access your registry:
docker login YOUR_REGISTRY -u YOUR_USERNAME -p YOUR_PASSWORD
Push the images to your registry
docker-compose push
>Note: The registry is configured in the .env file inside the root folder. **If you edit the .env file, make sure you run the the emulator afterwards** to update the cloud IoT Edge Device deployment configuration.
## Device Deployment
1. **Get the device connection string from Azure portal**, and on the device host run:
In the application root folder, type:
iotedgectl setup --connection-string "THE_DEVICE_CONNECTION_STRING" --auto-cert-gen-force-no-passwords
docker-compose build
>Note: The device name is configured in the appsettings.json of the emulator project. **Make sure you run the emulator and rebuilt the containers if you change that name**. The emulator will provision a new device if the device does not exist.
This will build your docker images for the device deployment. Final step is to push the images to the docker registry.
>Note: You need to provide the registry credentials to docker before you can push there:
docker login YOUR_REGISTRY -u YOUR_USERNAME -p YOUR_PASSWORD
Push the images to your registry
docker-compose push
>Note: The registry is configured in the .env file inside the root folder. **Make sure you run the the emulator if you edit the .env file**, to update the cloud IoT Device deployment configuration.
This was the final step. All IoT Edge containers are ready to be deployed to the device.
On the device host, **get the device connection string from Azure portal**, and run:
iotedgectl setup --connection-string "THE_DEVICE_CONNECTION_STRING" --auto-cert-gen-force-no-passwords
>Note: The device name is configured in the appsettings.json of the emulator project. **Make sure you run the emulator and rebuilt the containers if you change that name**. The emulator will provision a new device if the device does not exist.
if your registry requires authentication, you need to run
1. If your registry requires authentication, you need to run
iotedgectl login --address YOUR_REGISTRY_ADDRESS --username YOUR_REGISTRY_USERNAME --password YOUR_REGISTRY_PASSWORD
iotedgectl login --address YOUR_REGISTRY_ADDRESS --username YOUR_REGISTRY_USERNAME --password YOUR_REGISTRY_PASSWORD
Finally, start the runtime:
1. Finally, start the runtime:
iotedgectl start
iotedgectl start
Read more [here](https://docs.microsoft.com/en-us/azure/iot-edge/quickstart#configure-the-iot-edge-runtime) about the IoT Edge device deployment.

Просмотреть файл

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.26" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.48" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.26" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.48" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -1,7 +1,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.Azure.IoT.TypeEdge.DovEnv;
using Microsoft.Azure.IoT.TypeEdge.Host;
using Microsoft.Azure.IoT.TypeEdge.Host.DovEnv;
using Microsoft.Extensions.Configuration;
using TypeEdgeApplication.Shared;

Просмотреть файл

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<None Include="..\.env" Link=".env">
<None Include="..\.env" Link=".env" Visible="false">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
@ -15,7 +15,7 @@
<ItemGroup>
<PackageReference Include="rocksdb-native-arm" Version="5.4.6" />
<PackageReference Include="RocksDbNative" Version="5.4.6.10" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Host" Version="0.1.26" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Host" Version="0.1.48" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Proxy" Version="0.1.26" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Proxy" Version="0.1.48" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.26" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.48" />
</ItemGroup>
</Project>

Просмотреть файл

@ -5,4 +5,6 @@ services:
image: ${DOCKER_REGISTRY}TypeEdgeApplicationEmulatorImageName
build:
context: .
dockerfile: TypeEdgeApplication.Emulator/Dockerfile
dockerfile: TypeEdgeApplication.Emulator/Dockerfile
volumes:
- env:/env

Просмотреть файл

@ -1,4 +1,6 @@
version: '3.4'
volumes:
env:
services:
TypeEdgeApplication.Emulator:
@ -6,17 +8,22 @@ services:
build:
context: .
dockerfile: TypeEdgeApplication.Emulator/DockerfileEmpty
volumes:
- env:/env
TypeEdgeModule1:
image: ${DOCKER_REGISTRY}TypeEdgeModule1ImageName
build:
context: .
dockerfile: Modules/TypeEdgeModule1/Dockerfile
volumes:
- env:/env
TypeEdgeModule2:
image: ${DOCKER_REGISTRY}TypeEdgeModule2ImageName
build:
context: .
dockerfile: Modules/TypeEdgeModule2/Dockerfile
volumes:
- env:/env

Просмотреть файл

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Host" Version="0.1.26" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Host" Version="0.1.48" />
</ItemGroup>
</Project>

Просмотреть файл

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.26" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.48" />
</ItemGroup>
</Project>

Просмотреть файл

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Proxy" Version="0.1.26" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Proxy" Version="0.1.48" />
</ItemGroup>
</Project>

Просмотреть файл

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Host" Version="0.1.26" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Host" Version="0.1.48" />
</ItemGroup>
</Project>

Просмотреть файл

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.26" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge" Version="0.1.48" />
</ItemGroup>
</Project>

Просмотреть файл

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Proxy" Version="0.1.26" />
<PackageReference Include="Microsoft.Azure.IoT.TypeEdge.Proxy" Version="0.1.48" />
</ItemGroup>
</Project>

Просмотреть файл

@ -55,7 +55,7 @@ nuget.exe pack build\TypeEdgeApplication -Version %version%
move /Y *.nupkg ..\..\TypeEdgeNuGets
rem nuget.exe push ..\..\TypeEdgeNuGets\*%version%.nupkg -ApiKey VSTS
nuget.exe push ..\..\TypeEdgeNuGets\*%version%.nupkg -ApiKey VSTS
dotnet nuget locals http-cache --clear
dotnet new --debug:reinit

Двоичные данные
images/incontainer.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 489 KiB

Двоичные данные
images/vstsSecurity.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 50 KiB

Двоичные данные
images/vstsSecurity2.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 123 KiB