mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #35724 - Fixes issues 1 and 2 of the referenced bug ticket - Also fixes [this issue](https://github.com/fleetdm/fleet/pull/35739/files#r2548349172) ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually For unreleased bug fixes in a release candidate, one of: - [x] Alerted the release DRI if additional load testing is needed <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added upgrade code support to Windows maintained applications for improved MSI installer tracking and management. * **Documentation** * Updated Windows onboarding instructions with more precise manifest path guidance and concrete command examples for the maintained-apps generator. * **Tests** * Added comprehensive test coverage for upgrade code association with maintained applications. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
159 lines
4.7 KiB
Go
159 lines
4.7 KiB
Go
package maintained_apps
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
ma "github.com/fleetdm/fleet/v4/ee/maintained-apps"
|
|
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
kitlog "github.com/go-kit/log"
|
|
)
|
|
|
|
type appListing struct {
|
|
Name string `json:"name"`
|
|
Slug string `json:"slug"`
|
|
Platform string `json:"platform"`
|
|
UniqueIdentifier string `json:"unique_identifier"`
|
|
}
|
|
|
|
type AppsList struct {
|
|
Version uint `json:"version"`
|
|
Apps []appListing `json:"apps"`
|
|
}
|
|
|
|
const fmaOutputsBase = "https://raw.githubusercontent.com/fleetdm/fleet/refs/heads/main/ee/maintained-apps/outputs"
|
|
|
|
// Refresh fetches the latest information about maintained apps from FMA's
|
|
// apps list on GitHub and updates the Fleet database with the new information.
|
|
func Refresh(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger) error {
|
|
httpClient := fleethttp.NewClient(fleethttp.WithTimeout(10 * time.Second))
|
|
baseURL := fmaOutputsBase
|
|
if baseFromEnvVar := os.Getenv("FLEET_DEV_MAINTAINED_APPS_BASE_URL"); baseFromEnvVar != "" {
|
|
baseURL = baseFromEnvVar
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/apps.json", baseURL), nil)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "create http request")
|
|
}
|
|
|
|
res, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "execute http request")
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "read http response body")
|
|
}
|
|
|
|
switch res.StatusCode {
|
|
case http.StatusOK:
|
|
// success, go on
|
|
case http.StatusNotFound:
|
|
return ctxerr.New(ctx, "maintained apps list not found")
|
|
default:
|
|
if len(body) > 512 {
|
|
body = body[:512]
|
|
}
|
|
return ctxerr.Errorf(ctx, "apps list returned HTTP status %d: %s", res.StatusCode, string(body))
|
|
}
|
|
|
|
var appsList AppsList
|
|
if err := json.Unmarshal(body, &appsList); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "unmarshal apps list")
|
|
}
|
|
if appsList.Version != 2 {
|
|
return ctxerr.New(ctx, "apps list is an incompatible version")
|
|
}
|
|
|
|
var gotApps []string
|
|
|
|
for _, app := range appsList.Apps {
|
|
gotApps = append(gotApps, app.Slug)
|
|
|
|
if app.UniqueIdentifier == "" {
|
|
app.UniqueIdentifier = app.Name
|
|
}
|
|
|
|
if _, err = ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{
|
|
Name: app.Name,
|
|
Slug: app.Slug,
|
|
Platform: app.Platform,
|
|
UniqueIdentifier: app.UniqueIdentifier,
|
|
}); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "upsert maintained app")
|
|
}
|
|
}
|
|
|
|
// remove apps that were removed upstream
|
|
if err := ds.ClearRemovedFleetMaintainedApps(ctx, gotApps); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "clear removed maintained apps during refresh")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Hydrate pulls information from app-level FMA manifests info an FMA skeleton pulled from the database
|
|
func Hydrate(ctx context.Context, app *fleet.MaintainedApp) (*fleet.MaintainedApp, error) {
|
|
httpClient := fleethttp.NewClient(fleethttp.WithTimeout(10 * time.Second))
|
|
baseURL := fmaOutputsBase
|
|
if baseFromEnvVar := os.Getenv("FLEET_DEV_MAINTAINED_APPS_BASE_URL"); baseFromEnvVar != "" {
|
|
baseURL = baseFromEnvVar
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/%s.json", baseURL, app.Slug), nil)
|
|
if err != nil {
|
|
return nil, ctxerr.Wrap(ctx, err, "create http request")
|
|
}
|
|
|
|
res, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, ctxerr.Wrap(ctx, err, "execute http request")
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return nil, ctxerr.Wrap(ctx, err, "read http response body")
|
|
}
|
|
|
|
switch res.StatusCode {
|
|
case http.StatusOK:
|
|
// success, go on
|
|
case http.StatusNotFound:
|
|
return nil, ctxerr.New(ctx, "app not found in Fleet manifests")
|
|
default:
|
|
if len(body) > 512 {
|
|
body = body[:512]
|
|
}
|
|
return nil, ctxerr.Errorf(ctx, "manifest retrieval returned HTTP status %d: %s", res.StatusCode, string(body))
|
|
}
|
|
|
|
var manifest ma.FMAManifestFile
|
|
if err := json.Unmarshal(body, &manifest); err != nil {
|
|
return nil, ctxerr.Wrapf(ctx, err, "unmarshal FMA manifest for %s", app.Slug)
|
|
}
|
|
manifest.Versions[0].Slug = app.Slug
|
|
|
|
app.Version = manifest.Versions[0].Version
|
|
app.Platform = manifest.Versions[0].Platform()
|
|
app.InstallerURL = manifest.Versions[0].InstallerURL
|
|
app.SHA256 = manifest.Versions[0].SHA256
|
|
app.InstallScript = manifest.Refs[manifest.Versions[0].InstallScriptRef]
|
|
app.UninstallScript = manifest.Refs[manifest.Versions[0].UninstallScriptRef]
|
|
app.AutomaticInstallQuery = manifest.Versions[0].Queries.Exists
|
|
app.Categories = manifest.Versions[0].DefaultCategories
|
|
app.UpgradeCode = manifest.Versions[0].UpgradeCode
|
|
|
|
return app, nil
|
|
}
|