mirror of
https://github.com/fleetdm/fleet
synced 2026-05-01 02:17:21 +00:00
Updated the allowedCategories map to use 'Developer tools' instead of 'Developer Tools' to ensure category matching is consistent with how this category is displayed in the UI.
201 lines
5.8 KiB
Go
201 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"slices"
|
|
"strings"
|
|
|
|
maintained_apps "github.com/fleetdm/fleet/v4/ee/maintained-apps"
|
|
"github.com/fleetdm/fleet/v4/ee/maintained-apps/ingesters/homebrew"
|
|
"github.com/fleetdm/fleet/v4/ee/maintained-apps/ingesters/winget"
|
|
"github.com/fleetdm/fleet/v4/pkg/file"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
kitlog "github.com/go-kit/log"
|
|
"github.com/go-kit/log/level"
|
|
)
|
|
|
|
func main() {
|
|
slugPtr := flag.String("slug", "", "app slug")
|
|
debugPtr := flag.Bool("debug", false, "enable debug logging")
|
|
flag.Parse()
|
|
ctx := context.Background()
|
|
logger := kitlog.NewJSONLogger(os.Stderr)
|
|
lvl := level.AllowInfo()
|
|
if *debugPtr {
|
|
lvl = level.AllowDebug()
|
|
}
|
|
logger = level.NewFilter(logger, lvl)
|
|
logger = kitlog.With(logger, "ts", kitlog.DefaultTimestampUTC)
|
|
|
|
level.Info(logger).Log("msg", "starting maintained app ingestion")
|
|
|
|
ingesters := map[string]maintained_apps.Ingester{
|
|
"ee/maintained-apps/inputs/homebrew": homebrew.IngestApps,
|
|
"ee/maintained-apps/inputs/winget": winget.IngestApps,
|
|
}
|
|
|
|
for inputDir, ingest := range ingesters {
|
|
apps, err := ingest(ctx, logger, inputDir, *slugPtr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for _, app := range apps {
|
|
|
|
if app.IsEmpty() {
|
|
level.Info(logger).Log("msg", "skipping manifest update due to empty output", "slug", app.Slug)
|
|
continue
|
|
}
|
|
|
|
if err := processOutput(ctx, app); err != nil {
|
|
level.Error(logger).Log("msg", "failed to process maintained app output", "err", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func processOutput(ctx context.Context, app *maintained_apps.FMAManifestApp) error {
|
|
// validate categories before writing any files
|
|
if err := validateCategories(ctx, app); err != nil {
|
|
// Make the validation failure very obvious on stderr.
|
|
fmt.Fprintf(
|
|
os.Stderr,
|
|
"maintained-apps: fatal error processing %s: %v\n",
|
|
app.Slug,
|
|
err,
|
|
)
|
|
// Wrap so callers still see a proper error.
|
|
return ctxerr.Wrap(ctx, err, "validating categories")
|
|
}
|
|
|
|
if err := updateAppsListFile(ctx, app); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "updating apps list file")
|
|
}
|
|
app.UniqueIdentifier = "" // make sure we don't leak unique_identifier into individual app manifests
|
|
|
|
outFile := maintained_apps.FMAManifestFile{
|
|
Versions: []*maintained_apps.FMAManifestApp{app},
|
|
Refs: map[string]string{app.UninstallScriptRef: app.UninstallScript, app.InstallScriptRef: app.InstallScript},
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
encoder := json.NewEncoder(&buf)
|
|
encoder.SetEscapeHTML(false)
|
|
encoder.SetIndent("", " ")
|
|
if err := encoder.Encode(outFile); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "marshaling output app manifest")
|
|
}
|
|
outBytes := buf.Bytes()
|
|
|
|
outDir := path.Join(maintained_apps.OutputPath, app.SlugAppName())
|
|
|
|
if err := os.MkdirAll(outDir, os.ModePerm); err != nil {
|
|
return ctxerr.Wrap(ctx, err)
|
|
}
|
|
outFilePath := path.Join(maintained_apps.OutputPath, fmt.Sprintf("%s.json", app.Slug))
|
|
outFileExists, err := file.Exists(outFilePath)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "checking if output json file exists")
|
|
}
|
|
|
|
// Overwrite the file unless frozen, since right now we're only caring about 1 version (latest). If we
|
|
// care about previous data, it will be in our Git history.
|
|
if !app.Frozen || !outFileExists {
|
|
if err := os.WriteFile(outFilePath, outBytes, 0o644); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "writing output json file")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Match types in frontend/interfaces/software.ts
|
|
var allowedCategories = map[string]struct{}{
|
|
"Browsers": {},
|
|
"Communication": {},
|
|
"Developer tools": {},
|
|
"Productivity": {},
|
|
"Security": {},
|
|
"Utilities": {},
|
|
}
|
|
|
|
func allowedCategoriesString() string {
|
|
cats := make([]string, 0, len(allowedCategories))
|
|
for c := range allowedCategories {
|
|
cats = append(cats, c)
|
|
}
|
|
slices.Sort(cats)
|
|
return strings.Join(cats, ", ")
|
|
}
|
|
|
|
// validateCategories ensures every category on the app is one of the supported values.
|
|
func validateCategories(ctx context.Context, app *maintained_apps.FMAManifestApp) error {
|
|
for _, c := range app.DefaultCategories {
|
|
if _, ok := allowedCategories[c]; !ok {
|
|
return ctxerr.New(ctx, fmt.Sprintf(
|
|
"invalid category %q for slug %s (allowed: %s)",
|
|
c, app.Slug, allowedCategoriesString(),
|
|
))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func updateAppsListFile(ctx context.Context, outApp *maintained_apps.FMAManifestApp) error {
|
|
appListFilePath := path.Join(maintained_apps.OutputPath, "apps.json")
|
|
inputJson, err := os.ReadFile(appListFilePath)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "reading output apps list file")
|
|
}
|
|
|
|
var outputAppsFile maintained_apps.FMAListFile
|
|
if err := json.Unmarshal(inputJson, &outputAppsFile); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "unmarshaling output apps list file")
|
|
}
|
|
|
|
var found bool
|
|
for _, a := range outputAppsFile.Apps {
|
|
if a.Slug == outApp.Slug {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
platform := outApp.Platform()
|
|
if platform == "" {
|
|
return ctxerr.New(ctx, fmt.Sprintf("invalid platform found for slug %s", outApp.Slug))
|
|
}
|
|
|
|
outputAppsFile.Apps = append(outputAppsFile.Apps, maintained_apps.FMAListFileApp{
|
|
Name: outApp.Name,
|
|
Slug: outApp.Slug,
|
|
Platform: platform,
|
|
UniqueIdentifier: outApp.UniqueIdentifier,
|
|
})
|
|
|
|
// Keep existing order
|
|
slices.SortFunc(outputAppsFile.Apps, func(a, b maintained_apps.FMAListFileApp) int { return strings.Compare(a.Slug, b.Slug) })
|
|
|
|
var buf bytes.Buffer
|
|
encoder := json.NewEncoder(&buf)
|
|
encoder.SetEscapeHTML(false)
|
|
encoder.SetIndent("", " ")
|
|
if err := encoder.Encode(outputAppsFile); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "marshaling updated output apps file")
|
|
}
|
|
updatedFile := buf.Bytes()
|
|
|
|
if err := os.WriteFile(appListFilePath, updatedFile, 0o644); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "writing updated output apps file")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|