You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Expose the multi-package model through the REST API. Add a packages[] array to the software-title list and detail responses, keep software_package as the first-added package for backwards compatibility, let upload add a package to an existing title, and let edit and delete target a specific package.
Depends on the data-model foundation (datastore list-all read and first-added read). Can be built in parallel with the install-precedence and GitOps sub-issues.
Response structs
Add Packages []SoftwareInstaller to fleet.SoftwareTitle (server/fleet/software.go:410, alongside SoftwarePackage at :443).
Add a list-shaped Packages []SoftwarePackageOrApp to fleet.SoftwareTitleListResult (:505, alongside :529).
PATCH /api/_version_/fleet/software/titles/{id}/package (handler.go:432): operate on a specific package. Confirm the request identifies the package (installer_id) when a title has several. When a file replacement's hash matches a different package on the same title (UpdateSoftwareInstaller in ee/server/service/software_installers.go, after the new storage_id is computed near :501), return a friendly 409 (Couldn't edit. <file> package is already added (same SHA-256 hash).) instead of the raw 1062 the dedup_token key throws. Reuse Multiple packages: data model, migration, hash-dedupe guard, and multi-active regressions #48396's hash check with an exclude-self parameter (existingInstaller.ID); applies to script-only too (the file is the script).
Per-package delete: extend DELETE /api/_version_/fleet/software/titles/{title_id}/available_for_install (handler.go:433) to accept a specific installer_id, deleting one package while leaving the title and remaining packages intact. Deleting the last package (or an explicit title delete) keeps today's behavior. Follow the existing request/response struct patterns in server/service/software_installers.go.
Match the REST shapes documented in #48265 (trimmed for list, full for detail; packages.status counts combine automations, setup experience, and manual installs).
Add: uploading a second package to a title succeeds and appears in packages[]; FMA/VPP titles still reject custom packages with the documented copy.
Edit: PATCH updates the targeted package only. Replacing a file with a sibling package's hash returns the friendly 409 and leaves both packages unchanged; replacing with a new hash succeeds; re-saving the same file is a no-op. Covered for .pkg/.msi/.deb and script-only.
Delete: deleting one of several packages leaves the title and the rest intact; deleting the last package matches prior single-package delete behavior.
Authorization: observer, observer+, and gitops roles receive 403 on add/edit/delete at the API (backend-enforced). Free tier returns 402.
MYSQL_TEST=1 REDIS_TEST=1 go test ./server/service/... passes, including a test that adds two packages and asserts both response shapes.
Related user story
#28108
Task
Expose the multi-package model through the REST API. Add a
packages[]array to the software-title list and detail responses, keepsoftware_packageas the first-added package for backwards compatibility, let upload add a package to an existing title, and let edit and delete target a specific package.Depends on the data-model foundation (datastore list-all read and first-added read). Can be built in parallel with the install-precedence and GitOps sub-issues.
Response structs
Packages []SoftwareInstallertofleet.SoftwareTitle(server/fleet/software.go:410, alongsideSoftwarePackageat:443).Packages []SoftwarePackageOrApptofleet.SoftwareTitleListResult(:505, alongside:529).software_packagepopulated with the first-added package (nullwhen none). Why keep it: API clients and GitOps depend on it; [API] Add multiple custom packages for the same software title in the same fleet #48265 documents it as retained.Service population
SoftwareTitleByID(server/service/software_titles.go:143), replace the single-installer fetch with the list-all read from Multiple packages: data model, migration, hash-dedupe guard, and multi-active regressions #48396 and set bothPackagesandsoftware_package(first-added) at:206and:286.ListSoftwareTitles(:71), populate the trimmedpackages[]per title.Endpoints
POST /api/_version_/fleet/software/package(server/service/handler.go:429): allow adding to an existing title (drop the "already has an installer" path for custom packages; preserve FMA/VPP guards), subject to Multiple packages: data model, migration, hash-dedupe guard, and multi-active regressions #48396's hash guard and 10-limit.PATCH /api/_version_/fleet/software/titles/{id}/package(handler.go:432): operate on a specific package. Confirm the request identifies the package (installer_id) when a title has several. When a file replacement's hash matches a different package on the same title (UpdateSoftwareInstallerinee/server/service/software_installers.go, after the newstorage_idis computed near:501), return a friendly 409 (Couldn't edit. <file> package is already added (same SHA-256 hash).) instead of the raw 1062 thededup_tokenkey throws. Reuse Multiple packages: data model, migration, hash-dedupe guard, and multi-active regressions #48396's hash check with an exclude-self parameter (existingInstaller.ID); applies to script-only too (the file is the script).DELETE /api/_version_/fleet/software/titles/{title_id}/available_for_install(handler.go:433) to accept a specificinstaller_id, deleting one package while leaving the title and remaining packages intact. Deleting the last package (or an explicit title delete) keeps today's behavior. Follow the existing request/response struct patterns inserver/service/software_installers.go.Match the REST shapes documented in #48265 (trimmed for list, full for detail;
packages.statuscounts combine automations, setup experience, and manual installs).Condition of satisfaction
GET /software/titlesandGET /software/titles/:idreturnpackages[]matching [API] Add multiple custom packages for the same software title in the same fleet #48265;software_packageequals the first-added package and isnullwhen none.packages[]; FMA/VPP titles still reject custom packages with the documented copy..pkg/.msi/.deband script-only.MYSQL_TEST=1 REDIS_TEST=1 go test ./server/service/...passes, including a test that adds two packages and asserts both response shapes.