Add Winget support for Adobe Acrobat Pro: new input JSON, install/uninstall PowerShell scripts, and Windows output manifest (with script refs and installer metadata). Rename Homebrew input and apps listing to "Adobe Acrobat Pro" and add a Windows entry to apps.json. Improve winget ingester to try version directories in descending order, skip grouping dirs that don't contain expected manifests, fetch and unmarshal installer and locale manifests with better logging and error handling, and return a clear error when no valid version manifest is found. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for Adobe Acrobat Pro on Windows with automated install and uninstall capabilities. * **Bug Fixes** * Improved version handling during app installation to try multiple candidate versions if needed. * Enhanced error messaging for app uninstall validation. * **Updates** * Standardized Adobe Acrobat Pro product naming across platforms for consistency. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/43829?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
146 lines
4.3 KiB
Go
146 lines
4.3 KiB
Go
//go:build darwin || windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/fleetdm/fleet/v4/pkg/file"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
)
|
|
|
|
type AppCommander struct {
|
|
Name string
|
|
Slug string
|
|
UniqueIdentifier string
|
|
Version string
|
|
AppPath string
|
|
UninstallScript string
|
|
InstallScript string
|
|
cfg *Config
|
|
appLogger *slog.Logger
|
|
}
|
|
|
|
func (ac *AppCommander) isFrozen() (bool, error) {
|
|
inputPath := ac.cfg.inputsPath
|
|
parts := strings.Split(ac.Slug, "/")
|
|
if len(parts) != 2 {
|
|
return false, fmt.Errorf("invalid slug format: %s, expected <name>/<platform>", ac.Slug)
|
|
}
|
|
inputPath = filepath.Join(inputPath, parts[0]+".json")
|
|
|
|
fileBytes, err := os.ReadFile(inputPath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("reading app input file: %w", err)
|
|
}
|
|
|
|
var input struct {
|
|
Frozen bool `json:"frozen"`
|
|
}
|
|
if err := json.Unmarshal(fileBytes, &input); err != nil {
|
|
return false, fmt.Errorf("unmarshal app input file: %w", err)
|
|
}
|
|
|
|
return input.Frozen, nil
|
|
}
|
|
|
|
func (ac *AppCommander) extractAppVersion(installerTFR *fleet.TempFileReader) error {
|
|
if ac.Version == "latest" {
|
|
meta, err := file.ExtractInstallerMetadata(installerTFR)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ac.Version = meta.Version
|
|
err = installerTFR.Rewind()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ac *AppCommander) uninstallPreInstalled(ctx context.Context) {
|
|
ac.appLogger.InfoContext(ctx, "App is marked as pre-installed, attempting to run uninstall script...")
|
|
|
|
_, _, listError := ac.expectToChangeFileSystem(ctx,
|
|
func() error {
|
|
uninstalled := ac.uninstallApp(ctx)
|
|
if !uninstalled {
|
|
ac.appLogger.ErrorContext(ctx, "Failed to uninstall pre-installed app")
|
|
}
|
|
return nil
|
|
},
|
|
)
|
|
|
|
if listError != nil {
|
|
ac.appLogger.ErrorContext(ctx, fmt.Sprintf("Error listing %s directory: %v", ac.cfg.installationSearchDirectory, listError))
|
|
}
|
|
}
|
|
|
|
func (ac *AppCommander) uninstallApp(ctx context.Context) bool {
|
|
ac.appLogger.InfoContext(ctx, "Executing uninstall script for app...")
|
|
output, err := executeScript(ac.cfg, ac.UninstallScript)
|
|
if err != nil {
|
|
ac.appLogger.ErrorContext(ctx, fmt.Sprintf("Error uninstalling app: %v", err))
|
|
ac.appLogger.ErrorContext(ctx, fmt.Sprintf("Output: %s", output))
|
|
return false
|
|
}
|
|
ac.appLogger.DebugContext(ctx, fmt.Sprintf("Output: %s", output))
|
|
|
|
existance, err := appExists(ctx, ac.appLogger, ac.Name, ac.UniqueIdentifier, ac.Version, ac.AppPath)
|
|
if err != nil {
|
|
ac.appLogger.ErrorContext(ctx, fmt.Sprintf("Error checking if app exists after uninstall: %v", err))
|
|
return false
|
|
}
|
|
if existance {
|
|
ac.appLogger.ErrorContext(ctx, fmt.Sprintf("App still present after uninstall (expected no match for version '%s' in programs)", ac.Version))
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (ac *AppCommander) expectToChangeFileSystem(ctx context.Context, changer func() error) (string, error, error) {
|
|
var preListError, postListError, listError error
|
|
appListPre, err := listDirectoryContents(ac.cfg.installationSearchDirectory)
|
|
if err != nil {
|
|
preListError = fmt.Errorf("Error listing %s directory: %v", ac.cfg.installationSearchDirectory, err)
|
|
}
|
|
|
|
changerError := changer()
|
|
|
|
appListPost, err := listDirectoryContents(ac.cfg.installationSearchDirectory)
|
|
if err != nil {
|
|
postListError = fmt.Errorf("Error listing %s directory: %v", ac.cfg.installationSearchDirectory, err)
|
|
}
|
|
|
|
appPath, changed := detectApplicationChange(ac.cfg.installationSearchDirectory, appListPre, appListPost)
|
|
if appPath == "" {
|
|
ac.appLogger.WarnContext(ctx, fmt.Sprintf("no changes detected in %s directory after running application script.", ac.cfg.installationSearchDirectory))
|
|
} else {
|
|
if changed {
|
|
ac.appLogger.InfoContext(ctx, fmt.Sprintf("New application detected at: %s", appPath))
|
|
} else {
|
|
ac.appLogger.InfoContext(ctx, fmt.Sprintf("Application removal detected at: %s", appPath))
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case preListError != nil && postListError != nil:
|
|
listError = fmt.Errorf("app pre list: %v, app post list: %v", preListError, postListError)
|
|
case preListError != nil:
|
|
listError = fmt.Errorf("app pre list: %v", preListError)
|
|
case postListError != nil:
|
|
listError = fmt.Errorf("app post list: %v", postListError)
|
|
}
|
|
|
|
return appPath, changerError, listError
|
|
}
|