From 9bee64bf2da4329217f356623ba635116bd8520d Mon Sep 17 00:00:00 2001 From: Ian Littman Date: Thu, 26 Jun 2025 14:29:23 -0500 Subject: [PATCH] Persist download URL when adding FMAs via non-GitOps API, fix software versions on GitOps YAML generation (#30331) Fixes #29618, #30282. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated automated tests - [x] Manual QA for all new/changed functionality --- changes/29618-fix-version-in-generate-gitops | 1 + changes/30282-software-url-in-fma-ui-upload | 1 + cmd/fleetctl/fleetctl/generate_gitops.go | 6 +---- cmd/fleetctl/fleetctl/generate_gitops_test.go | 19 +++------------ .../test_dir_premium/teams/team-a.yml | 2 +- ee/server/service/maintained_apps.go | 1 + ee/server/service/maintained_apps_test.go | 24 ++++++++++--------- .../datastore/mysql/maintained_apps_test.go | 4 ++++ server/datastore/mysql/software_installers.go | 6 +++-- 9 files changed, 29 insertions(+), 35 deletions(-) create mode 100644 changes/29618-fix-version-in-generate-gitops create mode 100644 changes/30282-software-url-in-fma-ui-upload diff --git a/changes/29618-fix-version-in-generate-gitops b/changes/29618-fix-version-in-generate-gitops new file mode 100644 index 0000000000..f9913d59c6 --- /dev/null +++ b/changes/29618-fix-version-in-generate-gitops @@ -0,0 +1 @@ +* Fixed software package version output in generated GitOps YAML diff --git a/changes/30282-software-url-in-fma-ui-upload b/changes/30282-software-url-in-fma-ui-upload new file mode 100644 index 0000000000..31b5739364 --- /dev/null +++ b/changes/30282-software-url-in-fma-ui-upload @@ -0,0 +1 @@ +* Recorded installer URL when a Fleet-maintained app is added via the web UI or REST API diff --git a/cmd/fleetctl/fleetctl/generate_gitops.go b/cmd/fleetctl/fleetctl/generate_gitops.go index 62b8cccb86..dd69a1ebfc 100644 --- a/cmd/fleetctl/fleetctl/generate_gitops.go +++ b/cmd/fleetctl/fleetctl/generate_gitops.go @@ -1122,10 +1122,6 @@ func (cmd *GenerateGitopsCommand) generateSoftware(filePath string, teamId uint, packages := make([]map[string]interface{}, 0) appStoreApps := make([]map[string]interface{}, 0) for _, sw := range software { - versions := make([]string, len(sw.Versions)) - for j, version := range sw.Versions { - versions[j] = version.Version - } softwareSpec := make(map[string]interface{}) switch { case sw.SoftwarePackage != nil: @@ -1133,7 +1129,7 @@ func (cmd *GenerateGitopsCommand) generateSoftware(filePath string, teamId uint, if sw.SoftwarePackage.Name != "" { pkgName = fmt.Sprintf(" (%s)", sw.SoftwarePackage.Name) } - comment := cmd.AddComment(filePath, fmt.Sprintf("%s%s version %s", sw.Name, pkgName, strings.Join(versions, ", "))) + comment := cmd.AddComment(filePath, fmt.Sprintf("%s%s version %s", sw.Name, pkgName, sw.SoftwarePackage.Version)) if sw.HashSHA256 == nil { cmd.Messages.Notes = append(cmd.Messages.Notes, Note{ Filename: filePath, diff --git a/cmd/fleetctl/fleetctl/generate_gitops_test.go b/cmd/fleetctl/fleetctl/generate_gitops_test.go index 1a0162d247..fb67798859 100644 --- a/cmd/fleetctl/fleetctl/generate_gitops_test.go +++ b/cmd/fleetctl/fleetctl/generate_gitops_test.go @@ -201,31 +201,18 @@ func (MockClient) ListSoftwareTitles(query string) ([]fleet.SoftwareTitleListRes case "available_for_install=1&team_id=1": return []fleet.SoftwareTitleListResult{ { - ID: 1, - Name: "My Software Package", - Versions: []fleet.SoftwareVersion{{ - ID: 1, - Version: "1.0.0", - }, { - ID: 2, - Version: "2.0.0", - }}, + ID: 1, + Name: "My Software Package", HashSHA256: ptr.String("software-package-hash"), SoftwarePackage: &fleet.SoftwarePackageOrApp{ Name: "my-software.pkg", Platform: "darwin", + Version: "13.37", }, }, { ID: 2, Name: "My App Store App", - Versions: []fleet.SoftwareVersion{{ - ID: 3, - Version: "5.6.7", - }, { - ID: 4, - Version: "8.9.10", - }}, AppStoreApp: &fleet.SoftwarePackageOrApp{ AppStoreID: "com.example.team-software", }, diff --git a/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/team-a.yml b/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/team-a.yml index c85f9cc88a..9dee5b9eda 100644 --- a/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/team-a.yml +++ b/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/team-a.yml @@ -64,7 +64,7 @@ software: - Label C - Label D packages: - - hash_sha256: software-package-hash # My Software Package (my-software.pkg) version 1.0.0, 2.0.0 + - hash_sha256: software-package-hash # My Software Package (my-software.pkg) version 13.37 install_script: path: ../lib/team-a/scripts/my-software-package-darwin-install labels_include_any: diff --git a/ee/server/service/maintained_apps.go b/ee/server/service/maintained_apps.go index 7e01489f4c..abef692c40 100644 --- a/ee/server/service/maintained_apps.go +++ b/ee/server/service/maintained_apps.go @@ -150,6 +150,7 @@ func (svc *Service) AddFleetMaintainedApp( AutomaticInstall: automaticInstall, AutomaticInstallQuery: app.AutomaticInstallQuery, Categories: app.Categories, + URL: app.InstallerURL, } payload.Categories = server.RemoveDuplicatesFromSlice(payload.Categories) diff --git a/ee/server/service/maintained_apps_test.go b/ee/server/service/maintained_apps_test.go index 246f30e463..8459f46efd 100644 --- a/ee/server/service/maintained_apps_test.go +++ b/ee/server/service/maintained_apps_test.go @@ -281,17 +281,6 @@ func TestAddFleetMaintainedApp(t *testing.T) { UniqueIdentifier: "Internet Exploder", }, nil } - ds.MatchOrCreateSoftwareInstallerFunc = func(ctx context.Context, payload *fleet.UploadSoftwareInstallerPayload) (uint, uint, error) { - require.Equal(t, spoofedSHA, payload.StorageID) - require.Empty(t, payload.BundleIdentifier) - require.Equal(t, "Internet Exploder", payload.Title) - require.Equal(t, "programs", payload.Source) - require.Equal(t, "Hello World!", payload.InstallScript) - require.Equal(t, "Hello World!", payload.UninstallScript) - - // Can't easily inject a proper fleet.service so we bail early before NewActivity gets called and panics - return 0, 0, errors.New("forced error to short-circuit storage and activity creation") - } ds.GetSoftwareCategoryIDsFunc = func(ctx context.Context, names []string) ([]uint, error) { return []uint{}, nil } @@ -301,6 +290,18 @@ func TestAddFleetMaintainedApp(t *testing.T) { _, _ = w.Write(installerBytes) })) defer installerServer.Close() + ds.MatchOrCreateSoftwareInstallerFunc = func(ctx context.Context, payload *fleet.UploadSoftwareInstallerPayload) (uint, uint, error) { + require.Equal(t, spoofedSHA, payload.StorageID) + require.Empty(t, payload.BundleIdentifier) + require.Equal(t, "Internet Exploder", payload.Title) + require.Equal(t, "programs", payload.Source) + require.Equal(t, "Hello World!", payload.InstallScript) + require.Equal(t, "Hello World!", payload.UninstallScript) + require.Equal(t, installerServer.URL+"/iexplode.exe", payload.URL) + + // Can't easily inject a proper fleet.service so we bail early before NewActivity gets called and panics + return 0, 0, errors.New("forced error to short-circuit storage and activity creation") + } // Mock server to serve the manifest manifestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -326,6 +327,7 @@ func TestAddFleetMaintainedApp(t *testing.T) { err := json.NewEncoder(w).Encode(manifest) require.NoError(t, err) })) + t.Cleanup(manifestServer.Close) os.Setenv("FLEET_DEV_MAINTAINED_APPS_BASE_URL", manifestServer.URL) defer os.Unsetenv("FLEET_DEV_MAINTAINED_APPS_BASE_URL") diff --git a/server/datastore/mysql/maintained_apps_test.go b/server/datastore/mysql/maintained_apps_test.go index 78e5f1889d..4ade51c2c0 100644 --- a/server/datastore/mysql/maintained_apps_test.go +++ b/server/datastore/mysql/maintained_apps_test.go @@ -479,8 +479,12 @@ func testGetMaintainedAppBySlug(t *testing.T, ds *Datastore) { Platform: string(fleet.MacOSPlatform), BundleIdentifier: maintainedApp.UniqueIdentifier, ValidatedLabels: &fleet.LabelIdentsWithScope{}, + URL: "https://example.com/maintained-app-team1.pkg", }) require.NoError(t, err) + installer1, err := ds.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &team1.ID, titleId1, false) + require.NoError(t, err) + require.Equal(t, "https://example.com/maintained-app-team1.pkg", installer1.URL) // maintained app 2 maintainedApp2, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ diff --git a/server/datastore/mysql/software_installers.go b/server/datastore/mysql/software_installers.go index 7680d0a23b..a02f0328b7 100644 --- a/server/datastore/mysql/software_installers.go +++ b/server/datastore/mysql/software_installers.go @@ -211,8 +211,9 @@ INSERT INTO software_installers ( user_id, user_name, user_email, - fleet_maintained_app_id -) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT name FROM users WHERE id = ?), (SELECT email FROM users WHERE id = ?), ?)` + fleet_maintained_app_id, + url +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT name FROM users WHERE id = ?), (SELECT email FROM users WHERE id = ?), ?, ?)` args := []interface{}{ tid, @@ -233,6 +234,7 @@ INSERT INTO software_installers ( payload.UserID, payload.UserID, payload.FleetMaintainedAppID, + payload.URL, } res, err := tx.ExecContext(ctx, stmt, args...)