Skip to content

Commit

Permalink
Use package IDs list from database when needed for uninstall script c…
Browse files Browse the repository at this point in the history
…alc/diff

This allows us to not have to pull the existing installer file from file storage to perform edits.
  • Loading branch information
iansltx committed Sep 11, 2024
1 parent 27b2ff6 commit f75e569
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 94 deletions.
161 changes: 67 additions & 94 deletions ee/server/service/software_installers.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,124 +181,97 @@ func (svc *Service) UpdateSoftwareInstaller(ctx context.Context, titleID uint, p
SelfService: existingInstaller.SelfService,
}

// TODO drop installer pull once we have bundle IDs since at that point we'll have all the metadata we need from the DB
var payloadForNewInstallerFile *fleet.UploadSoftwareInstallerPayload
if payload.InstallerFile != nil || payload.PreInstallQuery != nil ||
payload.InstallScript != nil || payload.PostInstallScript != nil || payload.UninstallScript != nil {

// pull existing installer to calculate metadata for defaulting/validation

// check if the existingInstaller exists in the store
exists, err := svc.softwareInstallStore.Exists(ctx, existingInstaller.StorageID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "checking if existingInstaller exists")
}
if !exists {
return nil, ctxerr.Wrap(ctx, notFoundError{}, "does not exist in software existingInstaller store")
if payload.InstallerFile != nil {
payloadForNewInstallerFile = &fleet.UploadSoftwareInstallerPayload{
InstallerFile: payload.InstallerFile,
Filename: payload.Filename,
}

// get the installer from the store
existingInstallerFile, _, err := svc.softwareInstallStore.Get(ctx, existingInstaller.StorageID)
newInstallerExtension, err := svc.addMetadataToSoftwarePayload(ctx, payloadForNewInstallerFile)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "getting installer from store")
return nil, ctxerr.Wrap(ctx, err, "extracting updated installer metadata")
}

existingInstallerFileMeta, err := file.ExtractInstallerMetadata(existingInstallerFile)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "extracting existing installer metadata")
if newInstallerExtension != existingInstaller.Extension || payloadForNewInstallerFile.Source != software.Source {
return nil, &fleet.BadRequestError{
Message: "The selected package is for a different file type.",
InternalErr: ctxerr.Wrap(ctx, err, "installer extension mismatch"),
}
}

if payload.InstallerFile != nil {
payloadForNewInstallerFile = &fleet.UploadSoftwareInstallerPayload{
InstallerFile: payload.InstallerFile,
Filename: payload.Filename,
if payloadForNewInstallerFile.Title != software.Name {
return nil, &fleet.BadRequestError{
Message: "The selected package is for different software.",
InternalErr: ctxerr.Wrap(ctx, err, "installer software title mismatch"),
}
}

newInstallerExtension, err := svc.addMetadataToSoftwarePayload(ctx, payloadForNewInstallerFile)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "extracting updated installer metadata")
}
if payloadForNewInstallerFile.StorageID != existingInstaller.StorageID {
activity.SoftwarePackage = &payload.Filename
payload.StorageID = payloadForNewInstallerFile.StorageID
payload.Filename = payloadForNewInstallerFile.Filename
payload.Version = payloadForNewInstallerFile.Version
payload.PackageIDs = payloadForNewInstallerFile.PackageIDs

if newInstallerExtension != existingInstaller.Extension || payloadForNewInstallerFile.Source != software.Source {
return nil, &fleet.BadRequestError{
Message: "The selected package is for a different file type.",
InternalErr: ctxerr.Wrap(ctx, err, "installer extension mismatch"),
}
}
dirty = append(dirty, "Package")
} else { // noop if uploaded installer is identical to previous installer
payloadForNewInstallerFile = nil
}
}

if payloadForNewInstallerFile.Title != software.Name {
return nil, &fleet.BadRequestError{
Message: "The selected package is for different software.",
InternalErr: ctxerr.Wrap(ctx, err, "installer software title mismatch"),
}
}
if payloadForNewInstallerFile == nil { // fill in existing existingInstaller data to payload
payload.StorageID = existingInstaller.StorageID
payload.Filename = existingInstaller.Name
payload.Version = existingInstaller.Version
payload.PackageIDs = existingInstaller.PackageIDs()
}

if payloadForNewInstallerFile.StorageID != existingInstaller.StorageID {
activity.SoftwarePackage = &payload.Filename
payload.StorageID = payloadForNewInstallerFile.StorageID
payload.Filename = payloadForNewInstallerFile.Filename
payload.Version = payloadForNewInstallerFile.Version
payload.PackageIDs = payloadForNewInstallerFile.PackageIDs
// default pre-install query is blank, so blanking out the query doesn't have a semantic meaning we have to take care of
if payload.PreInstallQuery != nil && *payload.PreInstallQuery != existingInstaller.PreInstallQuery {
dirty = append(dirty, "PreInstallQuery")
}

dirty = append(dirty, "Package")
} else { // noop if uploaded installer is identical to previous installer
payloadForNewInstallerFile = nil
}
if payload.InstallScript != nil {
installScript := file.Dos2UnixNewlines(*payload.InstallScript)
if installScript == "" {
installScript = file.GetInstallScript(existingInstaller.Extension)
}

if payloadForNewInstallerFile == nil { // fill in existing existingInstaller data to payload
payload.StorageID = existingInstaller.StorageID
payload.Filename = existingInstaller.Name
payload.Version = existingInstaller.Version
payload.PackageIDs = existingInstallerFileMeta.PackageIDs
if installScript != existingInstaller.InstallScript {
dirty = append(dirty, "InstallScript")
payload.InstallScript = &installScript
}
}

// default pre-install query is blank, so blanking out the query doesn't have a semantic meaning we have to take care of
if payload.PreInstallQuery != nil && *payload.PreInstallQuery != existingInstaller.PreInstallQuery {
dirty = append(dirty, "PreInstallQuery")
if payload.PostInstallScript != nil {
postInstallScript := file.Dos2UnixNewlines(*payload.PostInstallScript)
if postInstallScript != existingInstaller.PostInstallScript {
dirty = append(dirty, "PostInstallScript")
payload.PostInstallScript = &postInstallScript
}
}

if payload.InstallScript != nil {
installScript := file.Dos2UnixNewlines(*payload.InstallScript)
if installScript == "" {
installScript = file.GetInstallScript(existingInstaller.Extension)
}

if installScript != existingInstaller.InstallScript {
dirty = append(dirty, "InstallScript")
payload.InstallScript = &installScript
}
if payload.UninstallScript != nil {
uninstallScript := file.Dos2UnixNewlines(*payload.UninstallScript)
if uninstallScript == "" { // extension can't change on an edit so we can generate off of the existing file
uninstallScript = file.GetUninstallScript(existingInstaller.Extension)
}

if payload.PostInstallScript != nil {
postInstallScript := file.Dos2UnixNewlines(*payload.PostInstallScript)
if postInstallScript != existingInstaller.PostInstallScript {
dirty = append(dirty, "PostInstallScript")
payload.PostInstallScript = &postInstallScript
}
payloadForUninstallScript := &fleet.UploadSoftwareInstallerPayload{
Extension: existingInstaller.Extension,
UninstallScript: uninstallScript,
PackageIDs: existingInstaller.PackageIDs(),
}
if payloadForNewInstallerFile != nil {
payloadForUninstallScript.PackageIDs = payloadForNewInstallerFile.PackageIDs
}

if payload.UninstallScript != nil {
uninstallScript := file.Dos2UnixNewlines(*payload.UninstallScript)
if uninstallScript == "" { // extension can't change on an edit so we can generate off of the existing file
uninstallScript = file.GetUninstallScript(existingInstaller.Extension)
}

payloadForUninstallScript := &fleet.UploadSoftwareInstallerPayload{
Extension: existingInstaller.Extension,
UninstallScript: uninstallScript,
PackageIDs: existingInstallerFileMeta.PackageIDs,
}
if payloadForNewInstallerFile != nil {
payloadForUninstallScript.PackageIDs = payloadForNewInstallerFile.PackageIDs
}

preProcessUninstallScript(payloadForUninstallScript)
if payloadForUninstallScript.UninstallScript != existingInstaller.UninstallScript {
uninstallScript = payloadForUninstallScript.UninstallScript
dirty = append(dirty, "UninstallScript")
payload.UninstallScript = &uninstallScript
}
preProcessUninstallScript(payloadForUninstallScript)
if payloadForUninstallScript.UninstallScript != existingInstaller.UninstallScript {
uninstallScript = payloadForUninstallScript.UninstallScript
dirty = append(dirty, "UninstallScript")
payload.UninstallScript = &uninstallScript
}
}

Expand Down
2 changes: 2 additions & 0 deletions server/datastore/mysql/software_installers.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ SELECT
si.team_id,
si.title_id,
si.storage_id,
si.package_ids,
si.filename,
si.extension,
si.version,
Expand Down Expand Up @@ -330,6 +331,7 @@ SELECT
si.team_id,
si.title_id,
si.storage_id,
si.package_ids,
si.filename,
si.extension,
si.version,
Expand Down
11 changes: 11 additions & 0 deletions server/fleet/software_installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ type SoftwareInstaller struct {
Version string `json:"version" db:"version"`
// Platform can be "darwin" (for pkgs), "windows" (for exes/msis) or "linux" (for debs).
Platform string `json:"platform" db:"platform"`
// PackageIDList is a comma-separated list of packages extracted from the installer
PackageIDList string `json:"-" db:"package_ids"`
// UploadedAt is the time the software package was uploaded.
UploadedAt time.Time `json:"uploaded_at" db:"uploaded_at"`
// InstallerID is the unique identifier for the software package metadata in Fleet.
Expand Down Expand Up @@ -119,6 +121,15 @@ func (s *SoftwareInstaller) AuthzType() string {
return "installable_entity"
}

// PackageIDs turns the comma-separated string from the database into a list (potentially zero-length) of string package IDs
func (s *SoftwareInstaller) PackageIDs() []string {
if s.PackageIDList == "" {
return []string{}
}

return strings.Split(s.PackageIDList, ",")
}

// SoftwareInstallerStatusSummary represents aggregated status metrics for a software installer package.
type SoftwareInstallerStatusSummary struct {
// Installed is the number of hosts that have the software package installed.
Expand Down

0 comments on commit f75e569

Please sign in to comment.