-
Notifications
You must be signed in to change notification settings - Fork 868
Devolutions Agent package broker experimental support #4826
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
Merged
Marc-André Moreau (mamoreau-devolutions)
merged 1 commit into
main
from
package-policies-new
Jul 22, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
src/UniGetUI.PackageEngine.AgentBroker/BrokerRequestBuilder.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| using Devolutions.Now.Policy.Api; | ||
| using Devolutions.Now.Policy.Client; | ||
| using UniGetUI.PackageEngine.Enums; | ||
| using UniGetUI.PackageEngine.Interfaces; | ||
| using UniGetUI.PackageEngine.Serializable; | ||
| // Aliased to avoid clashing with UniGetUI.PackageEngine.Enums.Architecture. | ||
| using BrokerArchitecture = Devolutions.Now.Policy.Api.Architecture; | ||
|
|
||
| namespace UniGetUI.PackageEngine.AgentBroker; | ||
|
|
||
| /// <summary> | ||
| /// Builds broker protocol requests from UniGetUI domain objects. | ||
| /// Maps IPackage + InstallOptions + OperationType into the canonical | ||
| /// <see cref="PackageRequest"/> consumed by the Devolutions Agent broker. | ||
| /// </summary> | ||
| public static class BrokerRequestBuilder | ||
| { | ||
| /// <summary>Build a broker request from UniGetUI package operation parameters.</summary> | ||
| /// <param name="effectiveInstallLocation"> | ||
| /// The install location resolved by the caller for this specific operation (e.g. the | ||
| /// registry-detected portable location for WinGet updates), or null to omit it. | ||
| /// </param> | ||
| public static PackageOperationRequest Build( | ||
| IPackage package, | ||
| InstallOptions options, | ||
| OperationType role, | ||
| string? effectiveInstallLocation = null) | ||
| { | ||
| // WinGet_DropArchAndScope is set after an "update not applicable" result to retry | ||
| // without the scope/architecture constraints; mirror the local WinGet behavior so | ||
| // the AutoRetry does not rebuild the same constrained request indefinitely. | ||
| bool dropArchAndScope = package.OverridenOptions.WinGet_DropArchAndScope; | ||
|
|
||
| return new PackageOperationRequest | ||
| { | ||
| RequestId = BrokerClient.GenerateRequestId(), | ||
| CreatedAt = DateTimeOffset.UtcNow, | ||
| Operation = MapOperation(role), | ||
| Manager = MapManagerName(package.Manager.Name), | ||
| CaptureOutput = true, | ||
| Source = new RequestSource | ||
| { | ||
| Name = package.Source.Name, | ||
| Url = package.Source.Url?.ToString(), | ||
| }, | ||
| Package = new RequestPackage | ||
| { | ||
| Id = package.Id, | ||
| Version = string.IsNullOrEmpty(options.Version) ? null : options.Version, | ||
| Architecture = dropArchAndScope ? null : MapArchitecture(options.Architecture), | ||
| }, | ||
| Options = new RequestOptions | ||
| { | ||
| // The per-package scope override takes precedence over the saved options, | ||
| // matching the local WinGet execution path. | ||
| Scope = dropArchAndScope | ||
| ? null | ||
| : MapScope(package.OverridenOptions.Scope ?? options.InstallationScope), | ||
| Interactive = options.InteractiveInstallation, | ||
| SkipHashCheck = options.SkipHashCheck, | ||
| PreRelease = options.PreRelease, | ||
| CustomParameters = GetCustomParameters(options, role), | ||
| CustomInstallLocation = NullIfEmpty(effectiveInstallLocation), | ||
| // Kill/pre/post actions are owned by the broker for brokered operations: | ||
| // they are sent in the request (and skipped locally) so that policy is | ||
| // evaluated before anything runs and actions never execute twice. | ||
| KillBeforeOperation = options.KillBeforeOperation ?? [], | ||
| PreOperationCommand = GetPreCommand(options, role), | ||
| PostOperationCommand = GetPostCommand(options, role), | ||
| UninstallPrevious = role is OperationType.Update && options.UninstallPreviousVersionsOnUpdate, | ||
| // NOTE: SkipMinorUpdates is UniGetUI loader-side filtering and must not be | ||
| // mapped to the broker's NoUpgrade option. | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| private static Operation MapOperation(OperationType role) => role switch | ||
| { | ||
| OperationType.Install => Operation.Install, | ||
| OperationType.Update => Operation.Update, | ||
| OperationType.Uninstall => Operation.Uninstall, | ||
| _ => throw new ArgumentException($"Unsupported operation type: {role}"), | ||
| }; | ||
|
|
||
| /// <summary> | ||
| /// Maps UniGetUI manager names to the broker protocol canonical managers. | ||
| /// PowerShell 5 and PowerShell 7 are modeled as separate managers. | ||
| /// </summary> | ||
| private static ManagerName MapManagerName(string managerName) | ||
| { | ||
| if (managerName.Equals("Winget", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| return ManagerName.Winget; | ||
| } | ||
|
|
||
| if (managerName.Equals("PowerShell", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| return ManagerName.PowerShell; | ||
| } | ||
|
|
||
| if (managerName.Equals("PowerShell7", StringComparison.OrdinalIgnoreCase) || | ||
| managerName.Equals("pwsh", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| return ManagerName.PowerShell7; | ||
| } | ||
|
|
||
| throw new ArgumentException($"Unsupported manager for the broker: {managerName}"); | ||
| } | ||
|
|
||
| private static Scope? MapScope(string? scope) | ||
| { | ||
| if (string.IsNullOrEmpty(scope)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| return scope.ToLowerInvariant() switch | ||
| { | ||
| "user" => Scope.User, | ||
| "machine" => Scope.Machine, | ||
| "global" => Scope.Machine, | ||
| _ => null, | ||
| }; | ||
| } | ||
|
|
||
| private static BrokerArchitecture? MapArchitecture(string? architecture) | ||
| { | ||
| if (string.IsNullOrEmpty(architecture)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| return architecture.ToLowerInvariant() switch | ||
| { | ||
| "x86" => BrokerArchitecture.X86, | ||
| "x64" => BrokerArchitecture.X64, | ||
| "arm64" => BrokerArchitecture.Arm64, | ||
| "neutral" => BrokerArchitecture.Neutral, | ||
| _ => null, | ||
| }; | ||
| } | ||
|
|
||
| private static List<string> GetCustomParameters(InstallOptions options, OperationType role) => role switch | ||
| { | ||
| OperationType.Install => options.CustomParameters_Install ?? [], | ||
| OperationType.Update => options.CustomParameters_Update ?? [], | ||
| OperationType.Uninstall => options.CustomParameters_Uninstall ?? [], | ||
| _ => [], | ||
| }; | ||
|
|
||
| private static string? GetPreCommand(InstallOptions options, OperationType role) => role switch | ||
| { | ||
| OperationType.Install => NullIfEmpty(options.PreInstallCommand), | ||
| OperationType.Update => NullIfEmpty(options.PreUpdateCommand), | ||
| OperationType.Uninstall => NullIfEmpty(options.PreUninstallCommand), | ||
| _ => null, | ||
| }; | ||
|
|
||
| private static string? GetPostCommand(InstallOptions options, OperationType role) => role switch | ||
| { | ||
| OperationType.Install => NullIfEmpty(options.PostInstallCommand), | ||
| OperationType.Update => NullIfEmpty(options.PostUpdateCommand), | ||
| OperationType.Uninstall => NullIfEmpty(options.PostUninstallCommand), | ||
| _ => null, | ||
| }; | ||
|
|
||
| private static string? NullIfEmpty(string? value) => | ||
| string.IsNullOrWhiteSpace(value) ? null : value; | ||
| } | ||
25 changes: 25 additions & 0 deletions
25
src/UniGetUI.PackageEngine.AgentBroker/UniGetUI.PackageEngine.AgentBroker.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFrameworks>$(SharedTargetFrameworks)</TargetFrameworks> | ||
| <RootNamespace>UniGetUI.PackageEngine.AgentBroker</RootNamespace> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Devolutions.Now.Policy.Api" Version="2026.7.13" /> | ||
| <PackageReference Include="Devolutions.Now.Policy.Client" Version="2026.7.13" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\UniGetUI.Core.Logger\UniGetUI.Core.Logging.csproj" /> | ||
| <ProjectReference Include="..\UniGetUI.Core.Settings\UniGetUI.Core.Settings.csproj" /> | ||
| <ProjectReference Include="..\UniGetUI.PackageEngine.Enums\UniGetUI.PackageEngine.Structs.csproj" /> | ||
| <ProjectReference Include="..\UniGetUI.PackageEngine.Interfaces\UniGetUI.PackageEngine.Interfaces.csproj" /> | ||
| <ProjectReference Include="..\UniGetUI.PackageEngine.Serializable\UniGetUI.PackageEngine.Serializable.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <Compile Include="..\SharedAssemblyInfo.cs" Link="SharedAssemblyInfo.cs" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.