-
Notifications
You must be signed in to change notification settings - Fork 1
Add a program which verifies that an identity has been correctly deleted #1283
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
base: main
Are you sure you want to change the base?
Changes from 20 commits
2f99e22
6dee93a
1eae560
b2b5ec8
0aeba1b
fd48095
bc74073
d00bc69
93020f9
65592cc
0087b7a
f9692c9
4d15bbc
e3f2021
a7f481f
63d9e25
c42fbec
186ed54
ce4c321
cd35c90
1e6e749
68c783b
2f1a772
395ccbf
920183a
7b4250a
8637877
23e5f0d
18c22df
9f2e612
634a201
94b3d97
ca37a82
b0ffc1c
2b2fb4d
aac6368
e329e5f
fc34241
8ba8f49
79e98d6
5c73924
bceecc3
1148aa1
6936ef3
631361c
c5ad7aa
d7caf17
ae149c8
ed09310
d1300d0
e5777b7
556b4b9
382e581
dbaa330
37a2cea
df5e405
ede5825
9884e96
dfed17f
0105ea2
50f2f6f
777a41f
aa3ee48
8271e56
4fa3017
9fd59bc
6985b86
6ec6c26
49d2efc
a73d51e
2f7108a
2918783
9a3fd87
c7c69b9
ee2042f
01d7db7
312ed0e
4b95189
c6a1e39
2d4c314
8101f04
1f79cf2
412b0c9
e5096b2
5e44cbb
e76f56f
ca21c4c
cfaa695
ea7eec0
22da433
f362ec7
7af156e
f8bf433
cfaa71d
0e9174a
19ee5e2
e99e730
6adac25
7173e09
10c90e4
32112c8
50a1749
5e6bc84
5d67d6b
dceabe2
0fe748c
b2f63b3
53f1768
f51a89f
1489355
3c8f8fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -173,7 +173,7 @@ jobs: | |||||
| strategy: | ||||||
| fail-fast: false | ||||||
| matrix: | ||||||
| database: [sqlserver, postgres] | ||||||
| database: [ sqlserver, postgres ] | ||||||
| test-project: | ||||||
| - display-name: admin-api | ||||||
| path: Applications/AdminApi/test/AdminApi.Tests.Integration | ||||||
|
|
@@ -255,7 +255,7 @@ jobs: | |||||
| strategy: | ||||||
| fail-fast: false | ||||||
| matrix: | ||||||
| database: [sqlserver, postgres] | ||||||
| database: [ sqlserver, postgres ] | ||||||
| needs: image-test-builds | ||||||
| steps: | ||||||
| - name: Checkout backbone repository | ||||||
|
|
@@ -357,3 +357,99 @@ jobs: | |||||
| run: dotnet restore /p:ContinuousIntegrationBuild=true ./Backbone.sln | ||||||
| - name: Validate Licenses | ||||||
| run: nuget-license --input ./Backbone.sln --allowed-license-types ./.ci/allowedLicenses.json --ignored-packages ./.ci/ignoredPackages.json --output table --error-only | ||||||
|
|
||||||
| verify-identity-deletion: | ||||||
| name: Validate Identity deletion (on ${{matrix.database.type}}) | ||||||
| runs-on: ubuntu-latest | ||||||
| strategy: | ||||||
| fail-fast: false | ||||||
| matrix: | ||||||
| database: | ||||||
| - type: postgres | ||||||
| env: | ||||||
| Database__Provider: Postgres | ||||||
| Database__ConnectionString: "Server=postgres;Database=enmeshed;User Id=devices;Password=Passw0rd;Port=5432" | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't you use the already existing appsettings.override..docker.json files instead of specifying the config via env vars? See the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am confused. Why does this work even though you the docker internal hostname |
||||||
| - type: sqlserver | ||||||
| env: | ||||||
| Database__Provider: SqlServer | ||||||
| Database__ConnectionString: "Server=sqlserver;Database=enmeshed;User Id=devices;Password=Passw0rd;TrustServerCertificate=True" | ||||||
| needs: image-test-builds | ||||||
| steps: | ||||||
| - name: Checkout | ||||||
| uses: actions/checkout@v5 | ||||||
|
|
||||||
| - name: Setup Dotnet | ||||||
| uses: actions/setup-dotnet@v4 | ||||||
| with: | ||||||
| dotnet-version: 9.x | ||||||
|
|
||||||
| - name: Cache NuGet packages | ||||||
| uses: actions/cache@v4 | ||||||
| with: | ||||||
| path: ~/.nuget/packages | ||||||
| key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} | ||||||
| restore-keys: ${{ runner.os }}-nuget- | ||||||
|
|
||||||
| - name: Download cached Docker images | ||||||
| uses: actions/download-artifact@v5 | ||||||
| with: | ||||||
| path: /tmp | ||||||
| pattern: docker-* | ||||||
| merge-multiple: true | ||||||
|
|
||||||
| - name: Load Docker images and build applications | ||||||
| run: | | ||||||
| { | ||||||
| ./.ci/loadDockerImages.sh | ||||||
| } & | ||||||
| { | ||||||
| # The following two lines are for the identity deletion job only | ||||||
| mv appsettings.override.json appsettings.override.json.bak | ||||||
| cp .ci/appsettings.override.${{matrix.database.type}}.local.json appsettings.override.json | ||||||
|
|
||||||
| dotnet restore ./Applications/IdentityDeletionVerifier/src/IdentityDeletionVerifier/IdentityDeletionVerifier.csproj | ||||||
| dotnet restore ./Applications/IdentityDeletionJobs/src/Job.IdentityDeletion/Job.IdentityDeletion.csproj | ||||||
| dotnet restore ./Applications/AdminCli/src/AdminCli/AdminCli.csproj | ||||||
|
|
||||||
| dotnet build --no-restore ./Applications/IdentityDeletionVerifier/src/IdentityDeletionVerifier/IdentityDeletionVerifier.csproj | ||||||
| dotnet build --no-restore ./Applications/IdentityDeletionJobs/src/Job.IdentityDeletion/Job.IdentityDeletion.csproj | ||||||
| dotnet build --no-restore ./Applications/AdminCli/src/AdminCli/AdminCli.csproj | ||||||
| } | ||||||
| wait | ||||||
|
|
||||||
| - name: Start compose stack | ||||||
| run: | | ||||||
| docker compose -f ./.ci/compose.test.yml -f ./.ci/compose.test.${{matrix.database.type}}.yml down -v | ||||||
| docker compose -f ./.ci/compose.test.yml -f ./.ci/compose.test.${{matrix.database.type}}.yml up --no-build --wait -d | ||||||
| docker compose -f ./.ci/compose.test.yml -f ./.ci/compose.test.${{matrix.database.type}}.yml wait admin-cli | ||||||
|
|
||||||
| - name: Create identities, relationships etc. and initiate deletion processes | ||||||
| run: dotnet run --no-build --no-restore --project ./Applications/IdentityDeletionVerifier/src/IdentityDeletionVerifier/IdentityDeletionVerifier.csproj init --consumerBaseUrl http://localhost:5000 --adminBaseUrl http://localhost:5173 | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| - name: Run Identity deletion job | ||||||
| run: dotnet run --no-build --no-restore --project ./Applications/IdentityDeletionJobs/src/Job.IdentityDeletion/Job.IdentityDeletion.csproj --Worker ActualDeletionWorker | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should ideally run in Docker (with the production image), so that we are as close to the production scenario as possible |
||||||
|
|
||||||
| - name: Export databse via Admin Cli | ||||||
| run: dotnet run --no-build --no-restore --project ./Applications/AdminCli/src/AdminCli/AdminCli.csproj database export --sensitive | ||||||
| env: ${{matrix.database.env}} | ||||||
|
|
||||||
| - name: Check exported database file | ||||||
| run: dotnet run --no-build --no-restore --project ./Applications/IdentityDeletionVerifier/src/IdentityDeletionVerifier/IdentityDeletionVerifier.csproj check | ||||||
|
|
||||||
| - name: Save Docker Logs | ||||||
| if: failure() | ||||||
| run: docker compose -f ./.ci/compose.test.yml -f ./.ci/compose.test.${{matrix.database.type}}.yml logs > logs.txt | ||||||
|
|
||||||
| - name: Archive logs | ||||||
| if: failure() | ||||||
| uses: actions/upload-artifact@v4 | ||||||
| with: | ||||||
| name: identity-deletion-verifier-docker-${{matrix.database.type}} | ||||||
| path: logs.txt | ||||||
|
|
||||||
| - name: Archive exported database and identity files | ||||||
| if: failure() | ||||||
| uses: actions/upload-artifact@v4 | ||||||
| with: | ||||||
| name: identity-deletion-verifier-exported-db-${{matrix.database.type}} | ||||||
| path: /tmp/enmeshed/backbone | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This configuration does everything at once? So:
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| <component name="ProjectRunConfigurationManager"> | ||
| <configuration default="false" name="Identity Deletion verifier" type="com.intellij.execution.configurations.multilaunch" factoryName="MultiLaunchConfiguration"> | ||
| <rows> | ||
| <ExecutableRowSnapshot> | ||
| <option name="condition"> | ||
| <ConditionSnapshot> | ||
| <option name="type" value="immediately" /> | ||
| </ConditionSnapshot> | ||
| </option> | ||
| <option name="executable"> | ||
| <ExecutableSnapshot> | ||
| <option name="id" value="runConfig:.NET Launch Settings Profile.IdentityDeletionVerifier: Init" /> | ||
| </ExecutableSnapshot> | ||
| </option> | ||
| </ExecutableRowSnapshot> | ||
| <ExecutableRowSnapshot> | ||
| <option name="condition"> | ||
| <ConditionSnapshot> | ||
| <option name="type" value="afterPreviousFinished" /> | ||
| </ConditionSnapshot> | ||
| </option> | ||
| <option name="executable"> | ||
| <ExecutableSnapshot> | ||
| <option name="id" value="runConfig:.NET Launch Settings Profile.Job.IdentityDeletion: Job.IdentityDeletion.ActualDeletion" /> | ||
| </ExecutableSnapshot> | ||
| </option> | ||
| </ExecutableRowSnapshot> | ||
| <ExecutableRowSnapshot> | ||
| <option name="condition"> | ||
| <ConditionSnapshot> | ||
| <option name="type" value="afterPreviousFinished" /> | ||
| </ConditionSnapshot> | ||
| </option> | ||
| <option name="executable"> | ||
| <ExecutableSnapshot> | ||
| <option name="id" value="runConfig:.NET Launch Settings Profile.AdminCli: Export database" /> | ||
| </ExecutableSnapshot> | ||
| </option> | ||
| </ExecutableRowSnapshot> | ||
| <ExecutableRowSnapshot> | ||
| <option name="condition"> | ||
| <ConditionSnapshot> | ||
| <option name="type" value="afterPreviousFinished" /> | ||
| </ConditionSnapshot> | ||
| </option> | ||
| <option name="executable"> | ||
| <ExecutableSnapshot> | ||
| <option name="id" value="runConfig:.NET Launch Settings Profile.IdentityDeletionVerifier: Check" /> | ||
| </ExecutableSnapshot> | ||
| </option> | ||
| </ExecutableRowSnapshot> | ||
| </rows> | ||
| <method v="2" /> | ||
| </configuration> | ||
| </component> |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,117 @@ | ||||||||||
| using System.CommandLine; | ||||||||||
| using System.IO.Compression; | ||||||||||
| using System.Text; | ||||||||||
| using Spectre.Console; | ||||||||||
|
|
||||||||||
| namespace Backbone.IdentityDeletionVerifier.Commands; | ||||||||||
|
|
||||||||||
| public class CheckCommand : Command | ||||||||||
| { | ||||||||||
| public CheckCommand() : base("check", "Check the exported database file for the given identity address in the temp directory") | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The project is called IdentityDeletionVerifier. Shouldn't this command then be called |
||||||||||
| { | ||||||||||
| SetAction(Handle); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private async Task<int> Handle(ParseResult _, CancellationToken cancellationToken) | ||||||||||
| { | ||||||||||
| if (!DirectoryExists()) | ||||||||||
| { | ||||||||||
| AnsiConsole.MarkupLineInterpolated($"[red bold]The temp directory[/][grey bold]{FilePaths.PATH_TO_TEMP_DIR} [/][red bold]doesn't exist.[/]"); | ||||||||||
| return -1; | ||||||||||
| } | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||
|
|
||||||||||
| if (!IdentitiesFileExists()) | ||||||||||
| { | ||||||||||
| AnsiConsole.MarkupLine("[red bold]The deleted identities file doesn't exist. Run the Init command first.[/]"); | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The first part of the error message sounds incorrect if you read it without any special knowledge. Because OF COURSE the identities file doesn't exist if it was deleted. 😉 |
||||||||||
| return -1; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (!ExportFileExists()) | ||||||||||
| { | ||||||||||
| AnsiConsole.MarkupLine("[red bold]No exported database file found. Run the Admin Cli Export Database command first.[/]"); | ||||||||||
| return -1; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| var a = await GetIdentityToCheck(); | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. weird name
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like the name of the method either. How about |
||||||||||
| if (a == null) | ||||||||||
| { | ||||||||||
| AnsiConsole.MarkupLineInterpolated($"[red bold]The identities file couldn't be read or has no Identity[/]"); | ||||||||||
| return -1; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| AnsiConsole.MarkupLineInterpolated($"[green bold]Identity to check:[/] [grey bold]{a}[/]"); | ||||||||||
|
|
||||||||||
| return await CheckExportFileForIdentities(GetLatestExportFile(), a); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private bool DirectoryExists() => Directory.Exists(FilePaths.PATH_TO_TEMP_DIR); | ||||||||||
| private bool IdentitiesFileExists() => File.Exists(FilePaths.PATH_TO_IDENTITIES_FILE); | ||||||||||
| private bool ExportFileExists() => Directory.EnumerateFiles(FilePaths.PATH_TO_TEMP_DIR).Any(FilePaths.EXPORT_FILE_PATTERN.IsMatch); | ||||||||||
| private string GetLatestExportFile() => Directory.EnumerateFiles(FilePaths.PATH_TO_TEMP_DIR).Where(e => FilePaths.EXPORT_FILE_PATTERN.IsMatch(e)).Max()!; | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest a (static?) class
|
||||||||||
|
|
||||||||||
| private async Task<string?> GetIdentityToCheck() | ||||||||||
| { | ||||||||||
| using var reader = new StreamReader(File.OpenRead(FilePaths.PATH_TO_IDENTITIES_FILE), Encoding.UTF8); | ||||||||||
|
|
||||||||||
| return await reader.ReadLineAsync(); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private async Task<int> CheckExportFileForIdentities(string exportFile, string identityToCheck) | ||||||||||
| { | ||||||||||
| return await AnsiConsole.Progress() | ||||||||||
| .AutoClear(false) | ||||||||||
| .HideCompleted(false) | ||||||||||
| .Columns( | ||||||||||
| new TaskDescriptionColumn(), | ||||||||||
| new ProgressBarColumn(), | ||||||||||
| new PercentageColumn(), | ||||||||||
| new RemainingTimeColumn(), | ||||||||||
| new SpinnerColumn(Spinner.Known.Clock) | ||||||||||
| ) | ||||||||||
| .StartAsync(async ctx => | ||||||||||
| { | ||||||||||
| using var archive = ZipFile.OpenRead(exportFile); | ||||||||||
|
|
||||||||||
| var found = 0; | ||||||||||
| var tasks = archive.Entries | ||||||||||
| .Select(e => ctx.AddTask(e.Name, autoStart: false)) | ||||||||||
| .ToList(); | ||||||||||
|
|
||||||||||
| foreach (var (index, entry) in archive.Entries.Index()) | ||||||||||
| { | ||||||||||
| var task = tasks[index]; | ||||||||||
| task.StartTask(); | ||||||||||
| found += await CheckCsvFileForIdentity(entry, identityToCheck, task); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return found; | ||||||||||
| }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private async Task<int> CheckCsvFileForIdentity(ZipArchiveEntry file, string identityToCheck, ProgressTask progressReporter) | ||||||||||
| { | ||||||||||
| using var reader = new StreamReader(file.Open(), Encoding.UTF8); | ||||||||||
| List<string> lines = []; | ||||||||||
| var found = 0; | ||||||||||
|
|
||||||||||
| while (!reader.EndOfStream) | ||||||||||
| lines.Add(await reader.ReadLineAsync() ?? string.Empty); | ||||||||||
|
|
||||||||||
| progressReporter.MaxValue = lines.Count; | ||||||||||
|
|
||||||||||
| foreach (var line in lines) | ||||||||||
| { | ||||||||||
| var count = line | ||||||||||
| .Split(',') | ||||||||||
| .Count(s => string.Equals(s, identityToCheck, StringComparison.OrdinalIgnoreCase)); | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a problem with this solution: if the address is concatenated with something else, it doesn't match. I'd suggest:
Suggested change
|
||||||||||
|
|
||||||||||
| if (count != 0) | ||||||||||
| AnsiConsole.MarkupLineInterpolated($"[red bold]Found[/] [grey bold]{count}[/] [red bold]occurrences in[/] [grey bold]{file.Name}[/][red bold]:[/] [grey bold]{line}[/]"); | ||||||||||
|
|
||||||||||
| found += count; | ||||||||||
| progressReporter.Increment(1); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return found; | ||||||||||
| } | ||||||||||
| } | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please stay with the typical config hierarchy, so that
In this case, this means: