Initial setup of environment service

This commit is contained in:
2024-01-13 21:20:09 -05:00
parent bacc7c1c67
commit b5f2a6b535
22 changed files with 892 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service", "Service\Service.csproj", "{AAF49637-5EAB-4D0F-A3EB-4421456DF709}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AAF49637-5EAB-4D0F-A3EB-4421456DF709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAF49637-5EAB-4D0F-A3EB-4421456DF709}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAF49637-5EAB-4D0F-A3EB-4421456DF709}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAF49637-5EAB-4D0F-A3EB-4421456DF709}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AF8B4FE8-1A8C-4189-8A16-BFD216D13047}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,195 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&#xD;
&lt;Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"&gt;&#xD;
&lt;TypePattern DisplayName="Non-reorderable types"&gt;&#xD;
&lt;TypePattern.Match&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Interface" /&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /&gt;&#xD;
&lt;HasAttribute Name="System.Runtime.InteropServices.ComImport" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;Kind Is="Struct" /&gt;&#xD;
&lt;HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" /&gt;&#xD;
&lt;HasAttribute Name="JetBrains.Annotations.NoReorder" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/TypePattern.Match&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"&gt;&#xD;
&lt;TypePattern.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Class" /&gt;&#xD;
&lt;HasMember&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;HasAttribute Name="Xunit.FactAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="Xunit.TheoryAttribute" Inherited="True" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/HasMember&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/TypePattern.Match&gt;&#xD;
&lt;Entry DisplayName="Setup/Teardown Methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;Kind Is="Constructor" /&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;ImplementsInterface Name="System.IDisposable" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Kind Order="Constructor" /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;HasAttribute Name="Xunit.FactAttribute" /&gt;&#xD;
&lt;HasAttribute Name="Xunit.TheoryAttribute" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"&gt;&#xD;
&lt;TypePattern.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Class" /&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestFixtureSourceAttribute" Inherited="True" /&gt;&#xD;
&lt;HasMember&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestAttribute" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestCaseAttribute" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/HasMember&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/TypePattern.Match&gt;&#xD;
&lt;Entry DisplayName="Setup/Teardown Methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestFixtureSetUpAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestFixtureTearDownAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.OneTimeSetUpAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.OneTimeTearDownAttribute" Inherited="True" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestAttribute" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestCaseAttribute" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;TypePattern DisplayName="Default Pattern"&gt;&#xD;
&lt;Entry DisplayName="Public Delegates" Priority="100"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Access Is="Public" /&gt;&#xD;
&lt;Kind Is="Delegate" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Public Enums" Priority="100"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Access Is="Public" /&gt;&#xD;
&lt;Kind Is="Enum" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Static Fields and Constants"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;Kind Is="Constant" /&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Field" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Kind Order="Constant Field" /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Fields"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Field" /&gt;&#xD;
&lt;Not&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;/Not&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Readonly /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Constructors"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Constructor" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Properties, Indexers"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;Kind Is="Property" /&gt;&#xD;
&lt;Kind Is="Indexer" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Interface Implementations" Priority="100"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD;
&lt;ImplementsInterface /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;ImplementsInterface Immediate="True" /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;Entry DisplayName="Nested Types"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Type" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;/Patterns&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mqtt/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -0,0 +1,51 @@
using System.Reflection;
using Dapper;
using DbUp;
using Microsoft.Data.SqlClient;
namespace ChrisKaczor.HomeMonitor.Environment.Service.Data;
public class Database(IConfiguration configuration)
{
private string GetConnectionString()
{
var connectionStringBuilder = new SqlConnectionStringBuilder
{
DataSource = configuration["Environment:Database:Host"],
UserID = configuration["Environment:Database:User"],
Password = configuration["Environment:Database:Password"],
InitialCatalog = configuration["Environment:Database:Name"],
TrustServerCertificate = bool.Parse(configuration["Environment:Database:TrustServerCertificate"] ?? "false")
};
return connectionStringBuilder.ConnectionString;
}
public void EnsureDatabase()
{
var connectionString = GetConnectionString();
DbUp.EnsureDatabase.For.SqlDatabase(connectionString);
var upgradeEngine = DeployChanges.To.SqlDatabase(connectionString).WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), s => s.Contains(".Schema.")).LogToConsole().Build();
upgradeEngine.PerformUpgrade();
}
private SqlConnection CreateConnection()
{
var connection = new SqlConnection(GetConnectionString());
connection.Open();
return connection;
}
public async Task StoreMessageAsync(Message message)
{
await using var connection = CreateConnection();
var query = ResourceReader.GetString("ChrisKaczor.HomeMonitor.Environment.Service.Data.Queries.CreateReading.sql");
await connection.QueryAsync(query, message);
}
}

View File

@@ -0,0 +1,25 @@
BEGIN TRANSACTION
INSERT Reading
(Timestamp, Name, Model, Temperature, Pressure, Humidity, Luminance, GasResistance, ColorTemperature, AirQualityIndex)
SELECT
@Timestamp,
@Name,
@Model,
@Temperature,
@Pressure,
@Humidity,
@Luminance,
@GasResistance,
@ColorTemperature,
@AirQualityIndex
WHERE NOT EXISTS
(
SELECT
1
FROM
Reading WITH (UPDLOCK, SERIALIZABLE)
WHERE Timestamp = @Timestamp AND Name = @Name AND Model = @Model
)
COMMIT TRANSACTION

View File

@@ -0,0 +1,14 @@
CREATE TABLE Reading
(
Timestamp datetimeoffset NOT NULL,
Name nvarchar(50) NOT NULL,
Model nvarchar(50) NOT NULL,
Temperature decimal(5, 2) NOT NULL,
Pressure decimal(6, 2) NOT NULL,
Humidity decimal(5, 2) NOT NULL,
Luminance int NOT NULL,
GasResistance int NOT NULL,
ColorTemperature int NOT NULL,
AirQualityIndex decimal(4, 1) NOT NULL,
CONSTRAINT reading_pk PRIMARY KEY (Timestamp, Name, Model)
);

View File

@@ -0,0 +1,17 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src
COPY ["./Service.csproj", "./"]
RUN dotnet restore "Service.csproj"
COPY . .
WORKDIR "/src"
RUN dotnet publish "Service.csproj" -c Release -o /app
FROM base AS final
RUN apk add --no-cache tzdata
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "ChrisKaczor.HomeMonitor.Environment.Service.dll"]

View File

@@ -0,0 +1,6 @@
@Environment_HostAddress = http://localhost:5234
GET {{Environment_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -0,0 +1,32 @@
using System.Text.Json.Serialization;
namespace ChrisKaczor.HomeMonitor.Environment.Service;
public class Message
{
[JsonPropertyName("model")]
public required string Model { get; set; }
[JsonPropertyName("nickname")]
public required string Name { get; set; }
[JsonPropertyName("readings")]
public required Readings Readings { get; set; }
[JsonPropertyName("timestamp")]
public required DateTimeOffset Timestamp { get; set; }
public decimal AirQualityIndex => Readings.AirQualityIndex;
public decimal ColorTemperature => Readings.ColorTemperature;
public decimal GasResistance => Readings.GasResistance;
public decimal Humidity => Readings.Humidity;
public decimal Luminance => Readings.Luminance;
public decimal Pressure => Readings.Pressure;
public decimal Temperature => Readings.Temperature;
}

View File

@@ -0,0 +1,68 @@
using System.Text.Json;
using ChrisKaczor.HomeMonitor.Environment.Service.Data;
using MQTTnet;
using MQTTnet.Client;
namespace ChrisKaczor.HomeMonitor.Environment.Service;
public class MessageHandler : IHostedService
{
private readonly IConfiguration _configuration;
private readonly Database _database;
private readonly IMqttClient _mqttClient;
private readonly MqttFactory _mqttFactory;
private readonly string _topic;
public MessageHandler(IConfiguration configuration, Database database)
{
_configuration = configuration;
_database = database;
_database.EnsureDatabase();
_topic = _configuration["Mqtt:Topic"] ?? string.Empty;
if (string.IsNullOrEmpty(_topic))
throw new InvalidOperationException("Topic not set");
_mqttFactory = new MqttFactory();
_mqttClient = _mqttFactory.CreateMqttClient();
_mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer(_configuration["Mqtt:Server"]).Build();
await _mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = _mqttFactory.CreateSubscribeOptionsBuilder().WithTopicFilter($"{_topic}/#").Build();
await _mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await _mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().Build(), CancellationToken.None);
}
private async Task OnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg)
{
var topic = arg.ApplicationMessage.Topic;
var payload = arg.ApplicationMessage.ConvertPayloadToString();
WriteLog($"Topic: {topic} = {payload}");
var message = JsonSerializer.Deserialize<Message>(payload);
if (message == null)
return;
await _database.StoreMessageAsync(message);
}
private static void WriteLog(string message)
{
Console.WriteLine(message);
}
}

View File

@@ -0,0 +1,26 @@
using ChrisKaczor.HomeMonitor.Environment.Service.Data;
namespace ChrisKaczor.HomeMonitor.Environment.Service;
public static class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddEnvironmentVariables();
builder.Services.AddControllers();
builder.Services.AddTransient<Database>();
builder.Services.AddHostedService<MessageHandler>();
var app = builder.Build();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}

View File

@@ -0,0 +1,10 @@
{
"profiles": {
"Environment": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,29 @@
using System.Text.Json.Serialization;
using JetBrains.Annotations;
namespace ChrisKaczor.HomeMonitor.Environment.Service;
[UsedImplicitly]
public class Readings
{
[JsonPropertyName("aqi")]
public decimal AirQualityIndex { get; set; }
[JsonPropertyName("color_temperature")]
public decimal ColorTemperature { get; set; }
[JsonPropertyName("gas_resistance")]
public decimal GasResistance { get; set; }
[JsonPropertyName("humidity")]
public decimal Humidity { get; set; }
[JsonPropertyName("luminance")]
public decimal Luminance { get; set; }
[JsonPropertyName("pressure")]
public decimal Pressure { get; set; }
[JsonPropertyName("temperature")]
public decimal Temperature { get; set; }
}

View File

@@ -0,0 +1,19 @@
using System.Reflection;
namespace ChrisKaczor.HomeMonitor.Environment.Service;
public static class ResourceReader
{
public static string GetString(string resourceName)
{
var assembly = Assembly.GetExecutingAssembly();
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null)
throw new Exception($"Resource {resourceName} not found in {assembly.FullName}. Valid resources are: {string.Join(", ", assembly.GetManifestResourceNames())}.");
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
}

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>false</InvariantGlobalization>
<RootNamespace>ChrisKaczor.HomeMonitor.Environment.Service</RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Remove="Data\Queries\CreateReading.sql" />
<None Remove="Data\Schema\1-Initial Schema.sql" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Data\Queries\CreateReading.sql" />
<EmbeddedResource Include="Data\Schema\1-Initial Schema.sql" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.28" />
<PackageReference Include="dbup-sqlserver" Version="5.0.37" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="MQTTnet.AspNetCore" Version="4.3.3.952" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
<Folder Include="Properties\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,20 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Mqtt": {
"Server": "172.23.10.3"
},
"Environment": {
"Database": {
"Host": "localhost",
"User": "sa",
"Password": "newpassword",
"Name": "Environment",
"TrustServerCertificate": true
}
}
}

View File

@@ -0,0 +1,22 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Mqtt": {
"Server": "mosquitto",
"Topic": "enviro"
},
"Environment": {
"Database": {
"Host": "",
"User": "",
"Password": "",
"Name": "Environment",
"TrustServerCertificate": false
}
}
}

View File

@@ -0,0 +1,72 @@
name: $(Rev:r)
pr: none
trigger:
batch: 'true'
branches:
include:
- master
paths:
include:
- Environment/Service
stages:
- stage: Build
jobs:
- job: Build
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Docker@0
displayName: 'Build an image'
inputs:
containerregistrytype: 'Container Registry'
dockerRegistryConnection: 'Docker Hub'
dockerFile: 'Environment/Service/Dockerfile'
imageName: 'ckaczor/home-monitor-environment-service:$(Build.BuildNumber)'
includeLatestTag: true
- task: Docker@0
displayName: 'Push an image'
inputs:
containerregistrytype: 'Container Registry'
dockerRegistryConnection: 'Docker Hub'
action: 'Push an image'
imageName: 'ckaczor/home-monitor-environment-service:$(Build.BuildNumber)'
includeLatestTag: true
- task: Bash@3
inputs:
targetType: 'inline'
script: 'sed -i s/#BUILD_BUILDNUMBER#/$BUILD_BUILDNUMBER/ Environment/Service/deploy/manifest.yaml'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: 'Environment/Service/deploy/manifest.yaml'
ArtifactName: 'Manifest'
publishLocation: 'Container'
- stage: Deploy
jobs:
- job: Deploy
pool:
vmImage: 'ubuntu-latest'
steps:
- task: DownloadBuildArtifacts@0
inputs:
artifactName: 'Manifest'
buildType: 'current'
downloadType: 'single'
downloadPath: '$(System.ArtifactsDirectory)'
- task: Kubernetes@1
inputs:
connectionType: 'Kubernetes Service Connection'
kubernetesServiceEndpoint: 'Kubernetes'
namespace: 'home-monitor'
command: 'apply'
useConfigurationFile: true
configuration: '$(System.ArtifactsDirectory)/Manifest/manifest.yaml'
secretType: 'dockerRegistry'
containerRegistryType: 'Container Registry'

View File

@@ -0,0 +1,158 @@
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: environment-database
namespace: home-monitor
labels:
app: environment-database
spec:
replicas: 1
selector:
matchLabels:
app: environment-database
serviceName: environment-database
template:
metadata:
labels:
app: environment-database
spec:
containers:
- name: environment-database
image: mcr.microsoft.com/mssql/server
terminationMessagePath: "/dev/termination-log"
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
env:
- name: SA_PASSWORD
valueFrom:
secretKeyRef:
name: environment-database-credentials
key: password
- name: ACCEPT_EULA
value: "Y"
- name: MSSQL_PID
value: Express
- name: TZ
value: America/New_York
volumeMounts:
- name: data
mountPath: /var/opt/mssql
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
nodeSelector:
kubernetes.io/hostname: kubernetes
schedulerName: default-scheduler
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: local-path
resources:
requests:
storage: 4Gi
---
kind: Service
apiVersion: v1
metadata:
name: environment-database
spec:
ports:
- name: client
port: 1433
selector:
app: environment-database
type: LoadBalancer
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: environment-service
namespace: home-monitor
labels:
app: environment-service
spec:
replicas: 1
selector:
matchLabels:
app: environment-service
template:
metadata:
labels:
app: environment-service
spec:
containers:
- name: environment-service
image: ckaczor/home-monitor-environment-service:#BUILD_BUILDNUMBER#
terminationMessagePath: "/dev/termination-log"
terminationMessagePolicy: File
imagePullPolicy: Always
securityContext:
privileged: true
env:
- name: Environment__Database__Host
value: environment-database
- name: Environment__Database__User
valueFrom:
secretKeyRef:
name: environment-database-credentials
key: username
- name: Environment__Database__Password
valueFrom:
secretKeyRef:
name: environment-database-credentials
key: password
- name: Hub__Environment
value: http://hub-service/environment
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
nodeSelector:
kubernetes.io/hostname: kubernetes
schedulerName: default-scheduler
---
kind: Service
apiVersion: v1
metadata:
name: environment-service
spec:
ports:
- name: client
port: 80
selector:
app: environment-service
type: ClusterIP
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
annotations:
kubernetes.io/ingress.class: traefik
creationTimestamp: null
name: environment
namespace: home-monitor
spec:
routes:
- kind: Rule
match: PathPrefix(`/api/environment`)
middlewares:
- name: api-environment
namespace: home-monitor
services:
- kind: Service
name: environment-service
namespace: home-monitor
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
creationTimestamp: null
name: api-environment
namespace: home-monitor
spec:
stripPrefix:
prefixes:
- /api/environment