Support advanced options for script-only packages (#48315)

**Related issue:** Resolves #42797

Adds support for pre-install query, post-install script, and uninstall
script on script-only packages (`.sh` and `.ps1`) across the API, UI,
and GitOps; previously these were silently stripped. The install script
remains the uploaded file's contents (file-driven) and is shown
read-only. Automatic install stays unsupported for script-only packages.

- **API** (`POST`/`PATCH /software/package`): stop stripping the fields;
validate post-install and uninstall scripts for script packages
- **GitOps**: allow
`uninstall_script`/`post_install_script`/`pre_install_query` paths
inline in the team YAML for script-only packages
- **UI**: show advanced options for `.sh`/`.ps1`; install script shown
read-only

  # Checklist for submitter

  - [x] Changes file added for user-visible changes in `changes/`.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.

  ## Testing

  - [x] Added/updated automated tests
  - [x] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Script-only packages (`.sh`/`.ps1`) now expose advanced
options—pre-install query, post-install script, and uninstall
script—consistently across the UI, REST API, and GitOps.
* Script-only packages display advanced options in the UI, and the
“Install script” editor can be made read-only where appropriate.
* **Bug Fixes**
* Preserved advanced option values for script-only packages during
upload, edits, and synchronization (including replace-file scenarios).
* Improved YAML generation and validation so supported fields are
included while unsupported ones are correctly rejected/omitted.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Carlo
2026-06-29 11:59:37 -04:00
committed by GitHub
parent 70b41063a4
commit 8cf1796a7d
11 changed files with 351 additions and 174 deletions
+18 -5
View File
@@ -283,8 +283,17 @@ type SoftwarePackage struct {
}
func (spec SoftwarePackage) HydrateToPackageLevel(packageLevel fleet.SoftwarePackageSpec, ext string) (fleet.SoftwarePackageSpec, error) {
if spec.InstallScript.Path != "" || spec.UninstallScript.Path != "" ||
spec.PostInstallScript.Path != "" || spec.URL != "" || spec.SHA256 != "" || spec.PreInstallQuery.Path != "" {
isScript := ext == ".sh" || ext == ".ps1"
// Script-only packages are configured inline in the team YAML, so their
// uninstall/post-install scripts and pre-install query are allowed here;
// other packages keep those in a package YAML. install_script, url, and hash
// are never valid at the team level.
if spec.InstallScript.Path != "" || spec.URL != "" || spec.SHA256 != "" ||
(!isScript && (spec.UninstallScript.Path != "" || spec.PostInstallScript.Path != "" || spec.PreInstallQuery.Path != "")) {
if isScript {
return packageLevel, fmt.Errorf("the software package defined in %s must not have install_script, URL, or hash specified at the team level", *spec.Path)
}
return packageLevel, fmt.Errorf("the software package defined in %s must not have icons, scripts, queries, URL, or hash specified at the team level", *spec.Path)
}
@@ -2188,13 +2197,17 @@ func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir strin
multiError = multierror.Append(multiError, err)
continue
}
// Script file becomes the install script for a script-only package
// The script file is the install script; the uninstall/post-install
// scripts and pre-install query come from the team YAML.
scriptSpec := fleet.SoftwarePackageSpec{
ReferencedYamlPath: resolvedPath,
Icon: teamLevelPackage.Icon,
UninstallScript: teamLevelPackage.UninstallScript,
PostInstallScript: teamLevelPackage.PostInstallScript,
PreInstallQuery: teamLevelPackage.PreInstallQuery,
}
// Icon path needs to be resolved, but since this function will set
// the install script it needs to be set to the correct path again.
// Resolve icon and script/query paths first; the install script path
// is set afterward since it's the script file itself.
scriptSpec = scriptSpec.ResolveSoftwarePackagePaths(baseDir)
scriptSpec.InstallScript.Path = resolvedPath