-
Notifications
You must be signed in to change notification settings - Fork 478
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
Maintained Apps: define app list, implement ingestion #21946
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the diff is huge, but a lot of this is testdata (the ~20 json files added for testing).
// StringOr is a JSON value that can be a string or a different type of object | ||
// (e.g. somewhat common for a string or an array of strings, but can also be | ||
// a string or an object, etc.). | ||
type StringOr[T any] struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is not technically an "optional JSON" type, but I feel it fits better in this package than floating elsewhere or creating another JSON-related package. Maybe we should rename the package, but I didn't go that far. Happy to adjust if anyone has a strong preference!
} | ||
|
||
func testIngestWithBrew(t *testing.T, ds *Datastore) { | ||
if os.Getenv("NETWORK_TEST") == "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test calls the real brew API, so I put it behind that existing NETWORK_TEST env var. I believe this will still run on each CI, though. Are we ok doing this? It will generate a bit of load on the API, but on the other hand it's good to catch any unforeseen changes on their part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
random thought: could we cache their responses in some other repo and then we only hit their API infrequently (once a day or something)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, I don't know ideally I'd like something easier, maybe we don't run that test in normal CI and only in our daily runs? I'll take a look if we already do this for some tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm looks like we don't really do that at the moment... it could run only if the race detector is enabled, but that feels hacky... I don't know, I'll leave it like that for now but open to ideas/suggestions! Maybe it's not that bad to let it run too, given that we will hit it daily from all fleet installations...
@@ -0,0 +1,97 @@ | |||
[ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the (embedded) JSON file that defines the apps we're interested in (the maintained apps). It contains bits of metadata information that we can't get otherwise from the Brew API.
case http.StatusOK: | ||
// success, go on | ||
case http.StatusNotFound: | ||
// TODO: delete the existing entry? do nothing and succeed? doing the latter for now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think doing nothing makes sense in that case? In all likelihood, the existing entry (if any) would still work - its installer URL is not tied to brew.
|
||
func installScriptForApp(app maintainedApp, cask *brewCask) string { | ||
// TODO: implement install script based on cask and app installer format | ||
return "install" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that logic part of the dmg/zip ticket?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this will be a significant effort. See
fleet/server/mdm/maintainedapps/ingest.go
Lines 181 to 220 in 7ac7ad3
type brewArtifact struct { | |
App []string `json:"app"` | |
// Pkg is a bit like Binary, it is an array with a string and an object as | |
// first two elements. The object has a choices field with an array of | |
// objects. See Microsoft Edge. | |
Pkg []optjson.StringOr[*brewPkgChoices] `json:"pkg"` | |
Uninstall []*brewUninstall `json:"uninstall"` | |
Zap []*brewZap `json:"zap"` | |
// Binary is an array with a string and an object as first two elements. See | |
// the "docker" and "firefox" casks. | |
Binary []optjson.StringOr[*brewBinaryTarget] `json:"binary"` | |
} | |
type brewPkgChoices struct { | |
// At the moment we don't care about the "choices" field on the pkg. | |
Choices []any `json:"choices"` | |
} | |
type brewBinaryTarget struct { | |
Target string `json:"target"` | |
} | |
// unlike brewArtifact, a single brewUninstall can have many fields set. | |
// All fields can have one or multiple strings (string or []string). | |
type brewUninstall struct { | |
LaunchCtl optjson.StringOr[[]string] `json:"launchctl"` | |
Quit optjson.StringOr[[]string] `json:"quit"` | |
PkgUtil optjson.StringOr[[]string] `json:"pkgutil"` | |
Script optjson.StringOr[[]string] `json:"script"` | |
// format: [0]=signal, [1]=process name | |
Signal optjson.StringOr[[]string] `json:"signal"` | |
Delete optjson.StringOr[[]string] `json:"delete"` | |
RmDir optjson.StringOr[[]string] `json:"rmdir"` | |
} | |
// same as brewUninstall, can be []string or string (see Microsoft Teams). | |
type brewZap struct { | |
Trash optjson.StringOr[[]string] `json:"trash"` | |
RmDir optjson.StringOr[[]string] `json:"rmdir"` | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those testdata files are a snapshot in time of what brew API returns for our apps. Useful for tests, to build a library from well-known, static values.
// It returns the expected results of the ingestion as a slice of | ||
// fleet.MaintainedApps with only a few fields filled - the result of | ||
// unmarshaling the testdata/expected_apps.json file. | ||
func IngestMaintainedApps(t *testing.T, ds fleet.Datastore) []*fleet.MaintainedApp { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heads-up, those should generally be used for tests when the maintained apps are needed, will generate a cohesive, valid set of apps.
// ExpectedAppTokens returns the list of app tokens (unique identifier) that are | ||
// expected to be in the maintained apps library after ingestion. The tokens are | ||
// taken from the apps.json list. | ||
func ExpectedAppTokens(t *testing.T) []string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is mostly useful to lightly validate that the real brew API test returned the expected tokens.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## feat-fleet-app-library #21946 +/- ##
=========================================================
Coverage ? 64.98%
=========================================================
Files ? 1497
Lines ? 116509
Branches ? 3450
=========================================================
Hits ? 75715
Misses ? 33769
Partials ? 7025
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! just 1 small question
}, | ||
{ | ||
"identifier": "box-drive", | ||
"bundle_identifier": "???", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this ("???"
) the expected string to use if we don't know the bundle identifier?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:D No, good thing to mention indeed, this will need to be figured out, I just couldn't find that app in dogfood to grab the bundle identifier. I'll try to get those filled for the next PR, testing those apps on my mac mini.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it as a checkbox to my ticket just to make sure I don't forget about it, thanks!
Partial implementation of #21773 , a subsequent PR will take care of creating the cron job and some changes needed to the ingestion, should not impact work that needs to read from the library.
Checklist for submitter
changes/
,orbit/changes/
oree/fleetd-chrome/changes
.See Changes files for more information.
SELECT *
is avoided, SQL injection is prevented (using placeholders for values in statements)