Skip to content

Add func workload install/uninstall commands#4926

Open
liliankasem wants to merge 1 commit intovnextfrom
liliankasem/feat/workload-install
Open

Add func workload install/uninstall commands#4926
liliankasem wants to merge 1 commit intovnextfrom
liliankasem/feat/workload-install

Conversation

@liliankasem
Copy link
Copy Markdown
Member

@liliankasem liliankasem commented May 1, 2026

Resolves #4898
Resolves #4899

Stacked on #4916 (workload entry-point discovery). Builds the install pipeline that consumes the scanner.

What lands

  • NuspecReader — XDocument-based, namespace-agnostic; reads minimal package metadata (id, version, package types, tags as aliases).
  • WorkloadInstaller — pipeline:
    1. Read .nuspec from staging dir.
    2. Validate FuncCliWorkload package type is present (per spec PR Add func workload design proposal #4923).
    3. Refuse to overwrite an existing install at IWorkloadPaths.GetInstallDirectory(packageId, version).
    4. Scan the staging dir for an IWorkload entry point (via PR Add workload entry-point discovery (attribute + scanner) #4916's IWorkloadEntryPointScanner) before the move, so failures don't pollute the final install location.
    5. Directory.Move staging → install dir.
    6. Persist the entry to the global manifest. On failure, rolls back the move.
  • func workload install --from <dir> — file-based install for now. NuGet feed acquisition is a follow-up; the resolver lands on top of this same IWorkloadInstaller.
  • func workload uninstall <packageId> [--version <v>] [--all]--all and --version are mutually exclusive. With neither flag: succeeds when exactly one version is installed; otherwise lists versions and asks the user to disambiguate.

Wired into DI through a new WorkloadInstallRegistration.AddWorkloadInstaller() extension called from Program.cs.

Out of scope (follow-ups)

  • NuGet feed acquisition (func workload install <packageId> taking a package id rather than a directory).
  • .nupkg file input (extract-to-temp + install). Trivial wrapper on top of this PR.
  • WorkloadCommand ownership refactor (PR-D in the stack).

Tests

28 new tests, all 116 in the suite passing:

  • 5 NuspecReader (happy path, namespace-agnostic, missing file/element, parse failures).
  • 8 WorkloadInstaller (happy, missing FuncCliWorkload type, missing nuspec, already installed, scanner failure, manifest write rollback, uninstall happy, uninstall no-such-entry).
  • 3 WorkloadInstallCommand (option shape, delegation + success message, GracefulException propagation).
  • 8 WorkloadUninstallCommand (no installs, single-no-flag, multiple-no-flag, --version, --version unknown, --all, --all+--version conflict, case-insensitive id match).

@liliankasem liliankasem requested review from a team as code owners May 1, 2026 23:55
Comment thread src/Func/Commands/Workload/WorkloadInstallCommand.cs Outdated
Comment thread src/Func/Commands/Workload/WorkloadInstallCommand.cs Outdated
Comment thread src/Func/Commands/Workload/WorkloadUninstallCommand.cs Outdated
Comment thread src/Func/Commands/Workload/WorkloadUninstallCommand.cs Outdated
Comment thread src/Func/Commands/Workload/WorkloadInstallCommand.cs Outdated
@liliankasem liliankasem force-pushed the liliankasem/feat/workload-discovery branch from 944c298 to 1641c81 Compare May 4, 2026 17:23
@liliankasem liliankasem force-pushed the liliankasem/feat/workload-install branch 2 times, most recently from 93644cd to a08e12f Compare May 5, 2026 00:04
Copy link
Copy Markdown
Contributor

@jviau jviau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two primary suggestions:

  1. Bring in NuGet library directly, use its types for loading and parsing a manifest.
  2. For first iteration of install, if you don't want to support feeds then add support for .nupkg on disk only. Don't add --version or --from yet, just func workload install <package-on-disk>.nupkg, we will detect "on disk" via the .nupkg extension.
    • nuget libraries probably have tooling to work with a .nupkg on disk directly, like extracting manifest from it.

Comment thread src/Func/Commands/Workload/WorkloadInstallCommand.cs Outdated
Comment thread src/Func/Commands/Workload/WorkloadInstallCommand.cs Outdated
Comment thread src/Func/Commands/Workload/WorkloadInstallCommand.cs Outdated
Comment thread src/Func/Commands/Workload/WorkloadInstallCommand.cs Outdated
Comment thread src/Func/Workloads/Install/NuspecMetadata.cs Outdated
Comment thread src/Func/Workloads/Install/WorkloadInstaller.cs Outdated
Copy link
Copy Markdown
Contributor

@jviau jviau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some of these comments are already discussed in the sync we had,

Description = "Package ID of the workload to uninstall.",
};

public Option<string?> VersionOption { get; } = new("--version")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add short-names for these options? -v for this one. Maybe -a for --all-versions?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-v is global verbose (we can discuss if we dont want this) - but -a for all makes sense

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually let's support -v and maybe just switch to diagnostics | -d instead of verbose for global?

var version = nuspec.GetVersion().ToNormalizedString();
var title = nuspec.GetTitle();
var description = nuspec.GetDescription();
var aliases = SplitTags(nuspec.GetTags());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to treat all tags as aliases? I think this would prevent workloads from using tags for other means. We should probably have a convention. Aliases are alias:<name> or workload_alias:<name>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching! Should be on alias:, will make sure to update that

}
catch (Exception ex) when (ex is InvalidDataException or PackagingException)
{
throw new GracefulException(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to include the inner exception?

private readonly IWorkloadEntryPointScanner _scanner = scanner ?? throw new ArgumentNullException(nameof(scanner));

/// <inheritdoc />
public async Task<InstalledWorkload> InstallFromPackageAsync(
Copy link
Copy Markdown
Contributor

@jviau jviau May 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to settle on the nupkg format. Nupkg can have many folders. Do we have any requirements on what folders, if any, they must put their entry point into?

We synced offline: will require packages have a workload.json.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will operate on workload.json at root for now - Fabio is working on the nupkg format

Installs workloads from a local .nupkg via NuGet.Packaging. The .nupkg is
extracted into ~/.azure-functions/workloads/<id>/<version>/, validated
against workload.json via IWorkloadMetadataReader, and recorded in the
global registry. Uninstall removes both the registry entry and the
on-disk install directory.

Surface:
  func workload install <package>           # currently a path to a .nupkg
  func workload uninstall <id> [--version <v>] [-a|--all-versions]

Aliases on the registry entry come from nuspec tags prefixed with
'alias:' so workloads can keep using other tags for marketplace search,
categories, etc. without leaking into the CLI alias surface.
@liliankasem liliankasem force-pushed the liliankasem/feat/workload-install branch from dc6bfce to cfa1257 Compare May 6, 2026 16:20
@liliankasem liliankasem changed the base branch from liliankasem/feat/workload-discovery to vnext May 6, 2026 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants