From ed9e39e2e6bc513ef7ec1b867499b99c91e7c8fa Mon Sep 17 00:00:00 2001 From: Zefek Date: Mon, 1 Jun 2026 21:35:55 +0200 Subject: [PATCH] Migrace na NET10 --- .github/workflows/buildpublish.yml | 2 +- IoTDeploy/GithubProvider.cs | 83 +++++++++++++++--------------- IoTDeploy/IoTDeploy.csproj | 4 +- IoTDeploy/Program.cs | 4 +- IoTDeploy/Strings.Designer.cs | 1 + IoTDeploy/Strings.cs.resx | 3 ++ IoTDeploy/Strings.de.resx | 3 ++ IoTDeploy/Strings.es.resx | 3 ++ IoTDeploy/Strings.fr.resx | 3 ++ IoTDeploy/Strings.pl.resx | 3 ++ IoTDeploy/Strings.resx | 3 ++ IoTDeploy/Strings.sk.resx | 3 ++ IoTDeployUI/Form1.cs | 12 ++--- IoTDeployUI/IoTDeployUI.csproj | 4 +- 14 files changed, 77 insertions(+), 54 deletions(-) diff --git a/.github/workflows/buildpublish.yml b/.github/workflows/buildpublish.yml index 8c90b3c..3726cf7 100644 --- a/.github/workflows/buildpublish.yml +++ b/.github/workflows/buildpublish.yml @@ -21,7 +21,7 @@ jobs: - name: Nastavení .NET uses: actions/setup-dotnet@v5 with: - dotnet-version: '9.0.x' + dotnet-version: '10.0.x' - name: Obnova závislostí (Restore) run: dotnet restore diff --git a/IoTDeploy/GithubProvider.cs b/IoTDeploy/GithubProvider.cs index d720371..d53111e 100644 --- a/IoTDeploy/GithubProvider.cs +++ b/IoTDeploy/GithubProvider.cs @@ -29,27 +29,23 @@ public record WorkflowInfo(long Id, string Name, string Path); public class GithubProvider { private static readonly ILogger Logger = Log.ForContext(); - - private GitHubClient gitHubClient; - private string _appId; - private long _installationId; - private string _owner; - private int _workflowTimeoutMinutes; - private string _pemKeyPath; + private readonly AppSettings settings; + private GitHubClient? gitHubClient; private AccessToken? _installationAccessToken; + private string? _pemKeyPath; + + private GitHubClient Client => + gitHubClient ?? throw new InvalidOperationException("GitHub klient není inicializován. Nejprve zavolejte Init()."); - public GithubProvider() + public GithubProvider(AppSettings settings) { + this.settings = settings; } - public async Task Init(AppSettings settings) + public async Task Init() { - _appId = settings.GitHub.AppId; - _installationId = settings.GitHub.InstallationId; - _owner = settings.GitHub.Owner; - _workflowTimeoutMinutes = settings.Runner.WorkflowTimeoutMinutes; + Logger.Information("Inicializuji GitHub App klienta (AppId={AppId}, Owner={Owner})", settings.GitHub.AppId, settings.GitHub.Owner); _pemKeyPath = FindPemFile(settings.GitHub.PemFilePattern); - Logger.Information("Inicializuji GitHub App klienta (AppId={AppId}, Owner={Owner})", _appId, _owner); gitHubClient = await CreateInstallationClient(); Logger.Information("GitHub klient úspěšně inicializován"); } @@ -65,6 +61,10 @@ private static string FindPemFile(string pattern) private async Task CreateInstallationClient() { string pemContent; + if (string.IsNullOrEmpty(_pemKeyPath)) + { + throw new InvalidOperationException(Strings.PrivateKeyNotFound); + } try { pemContent = File.ReadAllText(_pemKeyPath); @@ -78,14 +78,19 @@ private async Task CreateInstallationClient() { using var rsa = RSA.Create(); rsa.ImportFromPem(pemContent); - var jwt = GenerateJwt(_appId, rsa); + var jwt = GenerateJwt(settings.GitHub.AppId, rsa); var jwtClient = new GitHubClient(new ProductHeaderValue("IoTDeploy")) { Credentials = new Credentials(jwt, AuthenticationType.Bearer) }; - _installationAccessToken = await jwtClient.GitHubApps.CreateInstallationToken(_installationId); + _installationAccessToken = await jwtClient.GitHubApps.CreateInstallationToken(settings.GitHub.InstallationId); + + return new GitHubClient(new ProductHeaderValue("IoTDeploy")) + { + Credentials = new Credentials(_installationAccessToken.Token) + }; } catch (AuthorizationException) { @@ -93,13 +98,8 @@ private async Task CreateInstallationClient() } catch (NotFoundException) { - throw new InvalidOperationException(string.Format(Strings.InstallationNotFound, _installationId)); + throw new InvalidOperationException(string.Format(Strings.InstallationNotFound, settings.GitHub.InstallationId)); } - - return new GitHubClient(new ProductHeaderValue("IoTDeploy")) - { - Credentials = new Credentials(_installationAccessToken.Token) - }; } private string GenerateJwt(string appId, RSA rsaKey) @@ -123,7 +123,7 @@ private string GenerateJwt(string appId, RSA rsaKey) private async Task EnsureValidTokenAsync() { - if (_installationAccessToken == null || + if (gitHubClient == null || _installationAccessToken == null || DateTimeOffset.UtcNow >= _installationAccessToken.ExpiresAt.AddMinutes(-5)) { Logger.Debug("Obnovuji installation access token (vyprší {ExpiresAt})", _installationAccessToken?.ExpiresAt); @@ -135,7 +135,7 @@ private async Task EnsureValidTokenAsync() public async Task> GetRepositories() { await EnsureValidTokenAsync(); - var result = await gitHubClient.GitHubApps.Installation.GetAllRepositoriesForCurrent(); + var result = await Client.GitHubApps.Installation.GetAllRepositoriesForCurrent(); return result.Repositories; } @@ -144,7 +144,7 @@ public async Task> GetBranches(string repository) await EnsureValidTokenAsync(); try { - return await gitHubClient.Repository.Branch.GetAll(_owner, repository); + return (await Client.Repository.Branch.GetAll(settings.GitHub.Owner, repository)); } catch (NotFoundException) { @@ -155,7 +155,7 @@ public async Task> GetBranches(string repository) public async Task> GetEnvironments(string repository) { await EnsureValidTokenAsync(); - return (await gitHubClient.Repository.Environment.GetAll(_owner, repository)).Environments; + return (await Client.Repository.Environment.GetAll(settings.GitHub.Owner, repository)).Environments; } public async Task> GetWorkflows(string repository) @@ -163,7 +163,7 @@ public async Task> GetWorkflows(string repository) await EnsureValidTokenAsync(); try { - var response = await gitHubClient.Actions.Workflows.List(_owner, repository); + var response = await Client.Actions.Workflows.List(settings.GitHub.Owner, repository); return response.Workflows .Where(w => string.Equals(w.State.StringValue, "active", StringComparison.OrdinalIgnoreCase)) .Select(w => new WorkflowInfo(w.Id, w.Name, w.Path)) @@ -188,7 +188,7 @@ public async Task RunWorkflow(string repository, string branchName, long w Inputs = parameters.ToDictionary(kv => kv.Key, kv => (object)kv.Value) }; - await gitHubClient.Actions.Workflows.CreateDispatch(_owner, repository, workflowId, dispatch); + await Client.Actions.Workflows.CreateDispatch(settings.GitHub.Owner, repository, workflowId, dispatch); } catch (NotFoundException) { @@ -211,30 +211,31 @@ public async Task RunWorkflow(string repository, string branchName, long w private async Task WaitForQueuedRunAsync(string repository, long workflowId, DateTimeOffset createdAfter, IProgress progress, CancellationToken ct = default) { - var deadline = DateTimeOffset.UtcNow.AddMinutes(_workflowTimeoutMinutes); + var deadline = DateTimeOffset.UtcNow.AddMinutes(settings.Runner.WorkflowTimeoutMinutes); var attempt = 0; - Logger.Debug("Čekám na workflow run v repo={Repo}, workflow={WorkflowId} (timeout={Timeout}min)", repository, workflowId, _workflowTimeoutMinutes); + Logger.Debug("Čekám na workflow run v repo={Repo}, workflow={WorkflowId} (timeout={Timeout}min)", repository, workflowId, settings.Runner.WorkflowTimeoutMinutes); while (DateTimeOffset.UtcNow < deadline) { await Task.Delay(3000, ct); attempt++; progress.Report(string.Format(Strings.WaitingForWorkflow, attempt * 3)); - var runs = await gitHubClient.Actions.Workflows.Runs.ListByWorkflow( - _owner, repository, workflowId); + await EnsureValidTokenAsync(); + var runs = await Client.Actions.Workflows.Runs.ListByWorkflow( + settings.GitHub.Owner, repository, workflowId); var run = runs.WorkflowRuns.FirstOrDefault(r => r.CreatedAt >= createdAfter && r.Status.StringValue != "completed"); if (run != null) return run; } - Logger.Warning("Timeout při čekání na workflow run (repo={Repo}, workflow={WorkflowId}, timeout={Timeout}min)", repository, workflowId, _workflowTimeoutMinutes); - throw new TimeoutException(string.Format(Strings.WorkflowTimeout, _workflowTimeoutMinutes)); + Logger.Warning("Timeout při čekání na workflow run (repo={Repo}, workflow={WorkflowId}, timeout={Timeout}min)", repository, workflowId, settings.Runner.WorkflowTimeoutMinutes); + throw new TimeoutException(string.Format(Strings.WorkflowTimeout, settings.Runner.WorkflowTimeoutMinutes)); } public async Task> GetQueuedJobLabelsAsync(string repository, long runId, CancellationToken ct = default) { await EnsureValidTokenAsync(); - var jobs = await gitHubClient.Actions.Workflows.Jobs.List(_owner, repository, runId); + var jobs = await Client.Actions.Workflows.Jobs.List(settings.GitHub.Owner, repository, runId); var job = jobs.Jobs.FirstOrDefault(j => j.Status.StringValue == "queued") ?? jobs.Jobs.FirstOrDefault(); if (job == null) @@ -247,7 +248,7 @@ public async Task GetTokenForRunner(string repository) await EnsureValidTokenAsync(); try { - return await gitHubClient.Actions.SelfHostedRunners.CreateRepositoryRegistrationToken(_owner, repository); + return await Client.Actions.SelfHostedRunners.CreateRepositoryRegistrationToken(settings.GitHub.Owner, repository); } catch (NotFoundException) { @@ -271,8 +272,8 @@ public async Task> GetSuccessfulRunsWithArtifacts try { runs = string.IsNullOrEmpty(workflowName) - ? await gitHubClient.Actions.Workflows.Runs.List(_owner, repository, request, options) - : await gitHubClient.Actions.Workflows.Runs.ListByWorkflow(_owner, repository, workflowName, request, options); + ? await Client.Actions.Workflows.Runs.List(settings.GitHub.Owner, repository, request, options) + : await Client.Actions.Workflows.Runs.ListByWorkflow(settings.GitHub.Owner, repository, workflowName, request, options); } catch (NotFoundException) { @@ -288,7 +289,7 @@ public async Task> GetSuccessfulRunsWithArtifacts ListArtifactsResponse artifacts; try { - artifacts = await gitHubClient.Actions.Artifacts.ListWorkflowArtifacts(_owner, repository, run.Id); + artifacts = await Client.Actions.Artifacts.ListWorkflowArtifacts(settings.GitHub.Owner, repository, run.Id); } catch (Exception ex) { @@ -321,13 +322,13 @@ public async Task ResolveLatestArtifactAsync( public async Task GetWorkflowProgressAsync(string repository, long runId) { await EnsureValidTokenAsync(); - var run = await gitHubClient.Actions.Workflows.Runs.Get(_owner, repository, runId); + var run = await Client.Actions.Workflows.Runs.Get(settings.GitHub.Owner, repository, runId); var isCompleted = run.Status.StringValue == "completed"; var conclusion = isCompleted ? (run.Conclusion?.StringValue ?? "unknown") : null; try { - var jobs = await gitHubClient.Actions.Workflows.Jobs.List(_owner, repository, runId); + var jobs = await Client.Actions.Workflows.Jobs.List(settings.GitHub.Owner, repository, runId); var job = jobs.Jobs.FirstOrDefault(j => j.Status.StringValue == "in_progress") ?? jobs.Jobs.LastOrDefault(); diff --git a/IoTDeploy/IoTDeploy.csproj b/IoTDeploy/IoTDeploy.csproj index 1668d60..d4b4438 100644 --- a/IoTDeploy/IoTDeploy.csproj +++ b/IoTDeploy/IoTDeploy.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 enable enable @@ -10,7 +10,7 @@ - + diff --git a/IoTDeploy/Program.cs b/IoTDeploy/Program.cs index 8d9ce11..4868765 100644 --- a/IoTDeploy/Program.cs +++ b/IoTDeploy/Program.cs @@ -59,12 +59,12 @@ cts.Cancel(); }; -var githubProvider = new GithubProvider(); +var githubProvider = new GithubProvider(settings); var runner = new Runner(Guid.NewGuid().ToString("N")); try { Console.WriteLine(Strings.ConnectingToGitHub); - await githubProvider.Init(settings); + await githubProvider.Init(); var workflows = await githubProvider.GetWorkflows(cli.Repo); if (workflows.Count == 0) diff --git a/IoTDeploy/Strings.Designer.cs b/IoTDeploy/Strings.Designer.cs index 88f9047..e5926f8 100644 --- a/IoTDeploy/Strings.Designer.cs +++ b/IoTDeploy/Strings.Designer.cs @@ -84,6 +84,7 @@ internal static CultureInfo? Culture // GithubProvider.cs internal static string PemFileNotFound => ResourceManager.GetString("PemFileNotFound", resourceCulture)!; + internal static string PrivateKeyNotFound => ResourceManager.GetString("PrivateKeyNotFound", resourceCulture)!; internal static string CannotReadPrivateKey => ResourceManager.GetString("CannotReadPrivateKey", resourceCulture)!; internal static string AuthFailed => ResourceManager.GetString("AuthFailed", resourceCulture)!; internal static string InstallationNotFound => ResourceManager.GetString("InstallationNotFound", resourceCulture)!; diff --git a/IoTDeploy/Strings.cs.resx b/IoTDeploy/Strings.cs.resx index 6d07338..7cfaa16 100644 --- a/IoTDeploy/Strings.cs.resx +++ b/IoTDeploy/Strings.cs.resx @@ -166,6 +166,9 @@ Umístěte ho do složky aplikace. Nenalezen soubor privátního klíče GitHub App (pattern: '{0}'). Umístěte soubor do složky aplikace nebo upravte PemFilePattern v appsettings.json. + + Privátní klíč GitHub App nebyl nalezen. + Nelze načíst privátní klíč: {0} diff --git a/IoTDeploy/Strings.de.resx b/IoTDeploy/Strings.de.resx index 11a700d..ebcdf19 100644 --- a/IoTDeploy/Strings.de.resx +++ b/IoTDeploy/Strings.de.resx @@ -124,6 +124,9 @@ Legen Sie sie im Anwendungsordner ab. Private-Key-Datei der GitHub App nicht gefunden (Muster: '{0}'). Legen Sie die Datei im Anwendungsordner ab oder aktualisieren Sie PemFilePattern in appsettings.json. + + Privater Schlüssel der GitHub App nicht gefunden. + Privater Schlüssel kann nicht gelesen werden: {0} diff --git a/IoTDeploy/Strings.es.resx b/IoTDeploy/Strings.es.resx index 065ad10..f8a2f61 100644 --- a/IoTDeploy/Strings.es.resx +++ b/IoTDeploy/Strings.es.resx @@ -124,6 +124,9 @@ Colóquelo en la carpeta de la aplicación. Archivo de clave privada de GitHub App no encontrado (patrón: '{0}'). Coloque el archivo en la carpeta de la aplicación o actualice PemFilePattern en appsettings.json. + + Clave privada de GitHub App no encontrada. + No se puede leer la clave privada: {0} diff --git a/IoTDeploy/Strings.fr.resx b/IoTDeploy/Strings.fr.resx index 1bc332e..2842e5f 100644 --- a/IoTDeploy/Strings.fr.resx +++ b/IoTDeploy/Strings.fr.resx @@ -124,6 +124,9 @@ Placez-le dans le dossier de l'application. Fichier de clé privée de GitHub App introuvable (motif : '{0}'). Placez le fichier dans le dossier de l'application ou mettez à jour PemFilePattern dans appsettings.json. + + Clé privée de GitHub App introuvable. + Impossible de lire la clé privée : {0} diff --git a/IoTDeploy/Strings.pl.resx b/IoTDeploy/Strings.pl.resx index 6a9ef15..24d3630 100644 --- a/IoTDeploy/Strings.pl.resx +++ b/IoTDeploy/Strings.pl.resx @@ -124,6 +124,9 @@ Umieść go w folderze aplikacji. Nie znaleziono pliku klucza prywatnego GitHub App (wzorzec: '{0}'). Umieść plik w folderze aplikacji lub zaktualizuj PemFilePattern w appsettings.json. + + Nie znaleziono klucza prywatnego GitHub App. + Nie można odczytać klucza prywatnego: {0} diff --git a/IoTDeploy/Strings.resx b/IoTDeploy/Strings.resx index 9937c7c..bbb0d38 100644 --- a/IoTDeploy/Strings.resx +++ b/IoTDeploy/Strings.resx @@ -171,6 +171,9 @@ Place it in the application folder. GitHub App private key file not found (pattern: '{0}'). Place the file in the application folder or update PemFilePattern in appsettings.json. + + GitHub App private key not found. + Cannot read private key: {0} diff --git a/IoTDeploy/Strings.sk.resx b/IoTDeploy/Strings.sk.resx index 0f78a0a..1012b6a 100644 --- a/IoTDeploy/Strings.sk.resx +++ b/IoTDeploy/Strings.sk.resx @@ -124,6 +124,9 @@ Umiestnite ho do priečinka aplikácie. Súbor privátneho kľúča GitHub App nebol nájdený (vzor: '{0}'). Umiestnite súbor do priečinka aplikácie alebo upravte PemFilePattern v appsettings.json. + + Privátny kľúč GitHub App nebol nájdený. + Nie je možné načítať privátny kľúč: {0} diff --git a/IoTDeployUI/Form1.cs b/IoTDeployUI/Form1.cs index ec05ec7..b72ff2d 100644 --- a/IoTDeployUI/Form1.cs +++ b/IoTDeployUI/Form1.cs @@ -19,7 +19,7 @@ public Form1(AppSettings settings, string logPath) InitializeComponent(); this.settings = settings; _logPath = logPath; - githubProvider = new GithubProvider(); + githubProvider = new GithubProvider(settings); } private void label1_Click(object sender, EventArgs e) @@ -31,9 +31,9 @@ private async void Form1_Load(object sender, EventArgs e) SetUiBusy(Strings.ConnectingToGitHub); try { - await githubProvider.Init(settings); + await githubProvider.Init(); cmbRepository.Items.Clear(); - foreach (var repo in await githubProvider.GetRepositories()) + foreach (var repo in (await githubProvider.GetRepositories()).OrderBy(k => k.Name)) { cmbRepository.Items.Add(repo.Name); } @@ -189,14 +189,14 @@ private async void cmbRepository_SelectedIndexChanged(object sender, EventArgs e try { cmbBranch.Items.Clear(); - foreach (var branch in await githubProvider.GetBranches(repositoryName)) + foreach (var branch in (await githubProvider.GetBranches(repositoryName)).OrderBy(k => k.Name)) { cmbBranch.Items.Add(branch.Name); } cmbWorkflow.Items.Clear(); var workflows = await githubProvider.GetWorkflows(repositoryName); - foreach (var wf in workflows) + foreach (var wf in workflows.OrderBy(k => k.Name)) { cmbWorkflow.Items.Add(new WorkflowComboItem(wf)); } @@ -204,7 +204,7 @@ private async void cmbRepository_SelectedIndexChanged(object sender, EventArgs e cmbWorkflow.SelectedIndex = 0; cmbEnvironment.Items.Clear(); - foreach (var env in await githubProvider.GetEnvironments(repositoryName)) + foreach (var env in (await githubProvider.GetEnvironments(repositoryName)).OrderBy(k => k.Name)) { cmbEnvironment.Items.Add(env.Name); } diff --git a/IoTDeployUI/IoTDeployUI.csproj b/IoTDeployUI/IoTDeployUI.csproj index ca2b7f4..8f3677f 100644 --- a/IoTDeployUI/IoTDeployUI.csproj +++ b/IoTDeployUI/IoTDeployUI.csproj @@ -2,7 +2,7 @@ WinExe - net9.0-windows + net10.0-windows enable true enable @@ -11,7 +11,7 @@ - +