Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FMA: missing pieces #22593

Merged
merged 3 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion ee/server/service/maintained_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,27 @@ import (
"context"
"crypto/sha256"
"encoding/hex"
"net/url"
"os"
"path/filepath"
"strings"
"time"

"github.com/fleetdm/fleet/v4/pkg/file"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mdm/maintainedapps"
)

func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript string, selfService bool) error {
func (svc *Service) AddFleetMaintainedApp(
ctx context.Context,
teamID *uint,
appID uint,
installScript, preInstallQuery, postInstallScript, uninstallScript string,
selfService bool,
) error {
if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{TeamID: teamID}, fleet.ActionWrite); err != nil {
return err
}
Expand Down Expand Up @@ -59,6 +69,21 @@ func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, app
filename = app.Name
}

installScript = file.Dos2UnixNewlines(installScript)
if installScript == "" {
installScript = app.InstallScript
}

uninstallScript = file.Dos2UnixNewlines(uninstallScript)
if uninstallScript == "" {
uninstallScript = app.UninstallScript
}

installerURL, err := url.Parse(app.InstallerURL)
if err != nil {
return err
}

installerReader := bytes.NewReader(installerBytes)
payload := &fleet.UploadSoftwareInstallerPayload{
InstallerFile: installerReader,
Expand All @@ -68,13 +93,16 @@ func (svc *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, app
Version: app.Version,
Filename: filename,
Platform: string(app.Platform),
Source: "apps",
Extension: strings.TrimPrefix(filepath.Ext(installerURL.Path), "."),
BundleIdentifier: app.BundleIdentifier,
StorageID: app.SHA256,
FleetLibraryAppID: &app.ID,
PreInstallQuery: preInstallQuery,
PostInstallScript: postInstallScript,
SelfService: selfService,
InstallScript: installScript,
UninstallScript: uninstallScript,
}

// Create record in software installers table
Expand Down
4 changes: 3 additions & 1 deletion ee/server/service/software_installers.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ func preProcessUninstallScript(payload *fleet.UploadSoftwareInstallerPayload) {
// Replace $PACKAGE_ID in the uninstall script with the package ID(s).
var packageID string
switch payload.Extension {
case "dmg", "zip":
return
case "pkg":
var sb strings.Builder
_, _ = sb.WriteString("(\n")
Expand Down Expand Up @@ -1515,7 +1517,7 @@ func packageExtensionToPlatform(ext string) string {
switch ext {
case ".msi", ".exe":
requiredPlatform = "windows"
case ".pkg":
case ".pkg", ".dmg", ".zip":
requiredPlatform = "darwin"
case ".deb", ".rpm":
requiredPlatform = "linux"
Expand Down
15 changes: 13 additions & 2 deletions frontend/interfaces/package_type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const unixPackageTypes = ["pkg", "deb", "rpm"] as const;
const fleetMaintainedPackageTypes = ["dmg", "zip"] as const;
const unixPackageTypes = ["pkg", "deb", "rpm", "dmg", "zip"] as const;
const windowsPackageTypes = ["msi", "exe"] as const;
export const packageTypes = [
...unixPackageTypes,
Expand All @@ -7,7 +8,11 @@ export const packageTypes = [

export type WindowsPackageType = typeof windowsPackageTypes[number];
export type UnixPackageType = typeof unixPackageTypes[number];
export type PackageType = WindowsPackageType | UnixPackageType;
export type FleetMaintainedPackageType = typeof fleetMaintainedPackageTypes[number];
export type PackageType =
| WindowsPackageType
| UnixPackageType
| FleetMaintainedPackageType;

export const isWindowsPackageType = (s: any): s is WindowsPackageType => {
return windowsPackageTypes.includes(s);
Expand All @@ -17,6 +22,12 @@ export const isUnixPackageType = (s: any): s is UnixPackageType => {
return unixPackageTypes.includes(s);
};

export const isFleetMaintainedPackageType = (
s: any
): s is FleetMaintainedPackageType => {
return fleetMaintainedPackageTypes.includes(s);
};

export const isPackageType = (s: any): s is PackageType => {
return packageTypes.includes(s);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants";
import {
isPackageType,
isWindowsPackageType,
isFleetMaintainedPackageType,
PackageType,
} from "interfaces/package_type";

Expand Down Expand Up @@ -46,6 +47,10 @@ const getPostInstallHelpText = (pkgType: PackageType) => {
};

const getUninstallHelpText = (pkgType: PackageType) => {
if (isFleetMaintainedPackageType(pkgType)) {
return "Currently, shell scripts are supported";
}

return (
<>
$PACKAGE_ID will be populated with the {PKG_TYPE_TO_ID_TEXT[pkgType]} from
Expand Down
2 changes: 2 additions & 0 deletions frontend/services/entities/software.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ interface IAddFleetMaintainedAppPostBody {
pre_install_query?: string;
install_script?: string;
post_install_script?: string;
uninstall_script?: string;
self_service?: boolean;
}

Expand Down Expand Up @@ -376,6 +377,7 @@ export default {
pre_install_query: formData.preInstallQuery,
install_script: formData.installScript,
post_install_script: formData.postInstallScript,
uninstall_script: formData.uninstallScript,
self_service: formData.selfService,
};

Expand Down
4 changes: 2 additions & 2 deletions server/datastore/mysql/maintained_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ WHERE NOT EXISTS (
WHERE
st.bundle_identifier = fla.bundle_identifier
AND (
(si.platform = fla.platform AND si.team_id = ?)
(si.platform = fla.platform AND si.global_or_team_id = ?)
OR
(va.platform = fla.platform AND vat.team_id = ?)
(va.platform = fla.platform AND vat.global_or_team_id = ?)
)
)`

Expand Down
2 changes: 1 addition & 1 deletion server/fleet/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,7 @@ type Service interface {
// Fleet-maintained apps

// AddFleetMaintainedApp adds a Fleet-maintained app to the given team.
AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript string, selfService bool) error
AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript, uninstallScript string, selfService bool) error
// ListFleetMaintainedApps lists Fleet-maintained apps available to a specific team
ListFleetMaintainedApps(ctx context.Context, teamID uint, opts ListOptions) ([]MaintainedApp, *PaginationMetadata, error)
// GetFleetMaintainedApp returns a Fleet-maintained app by ID
Expand Down
40 changes: 26 additions & 14 deletions server/mdm/maintainedapps/ingest.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,11 @@ func (i ingester) ingestOne(ctx context.Context, app maintainedApp, client *http
return ctxerr.Wrapf(ctx, err, "parse URL for cask %s", app.Identifier)
}

installScript := installScriptForApp(app, &cask)
uninstallScript := uninstallScriptForApp(app, &cask)
installScript, err := installScriptForApp(app, &cask)
if err != nil {
return ctxerr.Wrapf(ctx, err, "create install script for cask %s", app.Identifier)
}
uninstallScript := uninstallScriptForApp(&cask)

_, err = i.ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{
Name: cask.Name[0],
Expand All @@ -155,16 +158,6 @@ func (i ingester) ingestOne(ctx context.Context, app maintainedApp, client *http
return ctxerr.Wrap(ctx, err, "upsert maintained app")
}

func installScriptForApp(app maintainedApp, cask *brewCask) string {
// TODO: implement install script based on cask and app installer format
return "install"
}

func uninstallScriptForApp(app maintainedApp, cask *brewCask) string {
// TODO: implement uninstall script based on cask and app installer format
return "uninstall"
}

type brewCask struct {
Token string `json:"token"`
FullToken string `json:"full_token"`
Expand Down Expand Up @@ -195,9 +188,28 @@ type brewArtifact struct {
Binary []optjson.StringOr[*brewBinaryTarget] `json:"binary"`
}

// The choiceChanges file is a property list containing an array of dictionaries. Each dictionary has the following three keys:
//
// Key Description
// choiceIdentifier Identifier for the choice to be modified (string)
// choiceAttribute One of the attribute names described below (string)
// attributeSetting A setting that depends on the choiceAttribute, described below (number or string)
//
// The choiceAttribute and attributeSetting values are as follows:
//
// choiceAttribute attributeSetting Description
// selected (number) 1 to select the choice, 0 to deselect it
// enabled (number) 1 to enable the choice, 0 to disable it
// visible (number) 1 to show the choice, 0 to hide it
// customLocation (string) path at which to install the choice (see below)
type brewPkgConfig struct {
ChoiceIdentifier string `json:"choiceIdentifier" plist:"choiceIdentifier"`
ChoiceAttribute string `json:"choiceAttribute" plist:"choiceAttribute"`
AttributeSetting int `json:"attributeSetting" plist:"attributeSetting"`
}

type brewPkgChoices struct {
// At the moment we don't care about the "choices" field on the pkg.
Choices []any `json:"choices"`
Choices []brewPkgConfig `json:"choices"`
}

type brewBinaryTarget struct {
Expand Down
Loading
Loading