Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug - backend dockerization #230

Merged
merged 3 commits into from
Jun 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ COPY --from=build /app ./
EXPOSE 8000

# Runtime user change to non-root for added security
RUN useradd -ms /bin/bash --uid 1001 isar
RUN chown -R 1001 /app
RUN chmod 755 /app
USER 1001

ENTRYPOINT ["dotnet", "api.dll", "--urls=http://0.0.0.0:8000"]
28 changes: 22 additions & 6 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ To set up the backend on **Linux**, install .NET for linux
[here](https://docs.microsoft.com/en-us/dotnet/core/install/linux).

For the configuration to be able to read secrets from the keyvault, you will need to have the client secret stored locally
in your secret manager as described in the [Configuration Section](#Configuration).
in your secret manager as described in the [Configuration Section](#Configuration).

For the MQTT client to function, the application expects a config variable named `mqtt-broker-password`, containing the password for the mqtt broker. This must either be stored in a connected keyvault or in the ASP.NET secret manager.
For the MQTT client to function, the application expects a config variable named `mqtt-broker-password`, containing the password for the mqtt broker.
This must either be stored in a connected keyvault or in the ASP.NET secret manager.

## Run

Expand All @@ -30,6 +31,22 @@ To change the ports of the application and various other launch settings (such a
Read more about the `launchSettings.json` file
[here](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-6.0&preserve-view=true&viewFallbackFrom=aspnetcore-2.2#lsj)

### Run in Docker

For the backend to work when dockerized, you need to have the client secret exposed as
an environment variable named `FLOTILLA_CLIENT_SECRET`.
The simplest way to do this in bash is to run

```
export FLOTILLA_CLIENT_SECRET=SuperSecret
```

To run the backend in docker, run the following command in the root folder of flotilla:

```
docker compose up backend --build
```

## Test

To unit test the backend, run the following command in the backend folder:
Expand All @@ -41,6 +58,7 @@ dotnet test
## Components

### MQTT Client

The MQTT client is implemented in [MqttService.cs](api/MQTT/MqttService.cs)
and runs as an ASP.NET
[BackgroundService](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-6.0&tabs=visual-studio#backgroundservice-base-class).
Expand All @@ -67,14 +85,14 @@ various app registrations used in development.

The configuration will also read from a configured azure keyvault, which can then be accessed the same way as any other config variables.
For this to work you will need to have the client secret stored locally in the secret manager as described below.
The client secret should be in the following format:
The client secret should be in the following format:

```
"AzureAd": {
"ClientSecret": "SECRET"
}
```


Any local secrets used for configuration should be added in the
[ASP.NET Secret Manager](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=linux#secret-manager).

Expand Down Expand Up @@ -106,5 +124,3 @@ dotnet format is used to detect naming conventions and other code-related issues
```
dotnet format --severity info
```


33 changes: 33 additions & 0 deletions backend/api/Configurations/ConfigurationBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Api.Configurations
{
public static class ConfigurationBuilderExtensions
{
/// <summary>
/// Creates the AZURE_CLIENT_ID and AZURE_TENANT_ID configuration values for the
/// <see href="https://docs.microsoft.com/en-us/dotnet/api/azure.identity.environmentcredential?view=azure-dotnet">Environment Credentials</see>
/// used by the application when dockerized.
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static void AddAzureEnvironmentVariables(this WebApplicationBuilder builder)
{
string? clientId = builder.Configuration
.GetSection("AzureAd")
.GetValue<string?>("ClientId");
if (clientId is not null)
{
Environment.SetEnvironmentVariable("AZURE_CLIENT_ID", clientId);
Console.WriteLine("'AZURE_CLIENT_ID' set to " + clientId);
}

string? tenantId = builder.Configuration
.GetSection("AzureAd")
.GetValue<string?>("TenantId");
if (clientId is not null)
{
Environment.SetEnvironmentVariable("AZURE_TENANT_ID", tenantId);
Console.WriteLine("'AZURE_TENANT_ID' set to " + tenantId);
}
}
}
}
51 changes: 27 additions & 24 deletions backend/api/EventHandlers/MqttEventHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,16 @@ private async void OnMissionUpdate(object? sender, MqttReceivedArgs mqttArgs)
return;
}

await _reportService.UpdateMissionStatus(mission.MissionId, status);
bool success = await _reportService.UpdateMissionStatus(mission.MissionId, status);

_logger.LogInformation(
"{time} - Mission {id} updated to {status} for {robot}",
mission.Timestamp,
mission.MissionId,
mission.Status,
mission.RobotId
);
if (success)
_logger.LogInformation(
"{time} - Mission {id} updated to {status} for {robot}",
mission.Timestamp,
mission.MissionId,
mission.Status,
mission.RobotId
);
}

private async void OnTaskUpdate(object? sender, MqttReceivedArgs mqttArgs)
Expand All @@ -80,15 +81,16 @@ private async void OnTaskUpdate(object? sender, MqttReceivedArgs mqttArgs)
return;
}

await _reportService.UpdateTaskStatus(task.TaskId, status);
bool success = await _reportService.UpdateTaskStatus(task.TaskId, status);

_logger.LogInformation(
"{time} - Task {id} updated to {status} for {robot}",
task.Timestamp,
task.TaskId,
task.Status,
task.RobotId
);
if (success)
_logger.LogInformation(
"{time} - Task {id} updated to {status} for {robot}",
task.Timestamp,
task.TaskId,
task.Status,
task.RobotId
);
}

private async void OnStepUpdate(object? sender, MqttReceivedArgs mqttArgs)
Expand All @@ -109,15 +111,16 @@ private async void OnStepUpdate(object? sender, MqttReceivedArgs mqttArgs)
return;
}

await _reportService.UpdateStepStatus(step.StepId, status);
bool success = await _reportService.UpdateStepStatus(step.StepId, status);

_logger.LogInformation(
"{time} - Step {id} updated to {status} for {robot}",
step.Timestamp,
step.StepId,
step.Status,
step.RobotId
);
if (success)
_logger.LogInformation(
"{time} - Step {id} updated to {status} for {robot}",
step.Timestamp,
step.StepId,
step.Status,
step.RobotId
);
}
}
}
7 changes: 6 additions & 1 deletion backend/api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);
builder.AddAzureEnvironmentVariables();

builder.Services.AddDbContext<FlotillaDbContext>(
options => options.UseInMemoryDatabase("flotilla")
Expand Down Expand Up @@ -56,9 +57,13 @@
}
);

// The ExcludeSharedTokenCacheCredential option is a recommended workaround by Azure for dockerization
// See https://github.com/Azure/azure-sdk-for-net/issues/17052
builder.Configuration.AddAzureKeyVault(
new Uri(builder.Configuration.GetSection("KeyVault")["VaultUri"]),
new DefaultAzureCredential()
new DefaultAzureCredential(
new DefaultAzureCredentialOptions { ExcludeSharedTokenCacheCredential = true }
)
);

var app = builder.Build();
Expand Down
18 changes: 12 additions & 6 deletions backend/api/Services/ReportService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public async Task<IEnumerable<Report>> ReadAll()
);
}

public async Task UpdateMissionStatus(string isarMissionId, ReportStatus reportStatus)
public async Task<bool> UpdateMissionStatus(string isarMissionId, ReportStatus reportStatus)
{
var report = await ReadByIsarMissionId(isarMissionId);
if (report is null)
Expand All @@ -90,15 +90,17 @@ public async Task UpdateMissionStatus(string isarMissionId, ReportStatus reportS
"Could not update mission status for ISAR mission with id: {id} as the report was not found",
isarMissionId
);
return;
return false;
}

report.ReportStatus = reportStatus;

await _context.SaveChangesAsync();

return true;
}

public async Task UpdateTaskStatus(string isarTaskId, IsarTaskStatus taskStatus)
public async Task<bool> UpdateTaskStatus(string isarTaskId, IsarTaskStatus taskStatus)
{
var task = await ReadIsarTaskById(isarTaskId);
if (task is null)
Expand All @@ -107,15 +109,17 @@ public async Task UpdateTaskStatus(string isarTaskId, IsarTaskStatus taskStatus)
"Could not update task status for ISAR task with id: {id} as the task was not found",
isarTaskId
);
return;
return false;
}

task.TaskStatus = taskStatus;

await _context.SaveChangesAsync();

return true;
}

public async Task UpdateStepStatus(string isarStepId, IsarStepStatus stepStatus)
public async Task<bool> UpdateStepStatus(string isarStepId, IsarStepStatus stepStatus)
{
var step = await ReadIsarStepById(isarStepId);
if (step is null)
Expand All @@ -124,12 +128,14 @@ public async Task UpdateStepStatus(string isarStepId, IsarStepStatus stepStatus)
"Could not update step status for ISAR step with id: {id} as the step was not found",
isarStepId
);
return;
return false;
}

step.StepStatus = stepStatus;

await _context.SaveChangesAsync();

return true;
}
}
}
5 changes: 5 additions & 0 deletions backend/backend.sln
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "api.test", "api.test\api.te
{8AB478C5-C4C7-4CDC-A691-F99C4A5C407A} = {8AB478C5-C4C7-4CDC-A691-F99C4A5C407A}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DFD9CB9F-ADDF-4167-A11C-E8EE83EE0548}"
ProjectSection(SolutionItems) = preProject
Dockerfile = Dockerfile
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
14 changes: 8 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
version: '3.7'
version: "3.7"
services:
frontend:
build: frontend
ports:
- '3001:3001'
- "3001:3001"
broker:
build: broker
ports:
- '1883:1883'
- '9001:9001'
- "1883:1883"
- "9001:9001"
backend:
build: backend
ports:
- '8000:8000'
- "8000:8000"
environment:
# Overwrites the ip address for the Mqtt service to connect to the Mqtt container:
- Mqtt__Host=broker
- ASPNETCORE_ENVIRONMENT=Development
- AzureAd__ClientId=ea4c7b92-47b3-45fb-bd25-a8070f0c495c
- AZURE_CLIENT_SECRET=${FLOTILLA_CLIENT_SECRET}