Skip to content

Android Load testing with osquery perf - #48535

Merged
lukeheath merged 19 commits into
mainfrom
26225-android-load-testing
Jul 17, 2026
Merged

Android Load testing with osquery perf#48535
lukeheath merged 19 commits into
mainfrom
26225-android-load-testing

Conversation

@ksykulev

@ksykulev ksykulev commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Related issue: Resolves #26225

go run ./cmd/android-amapi-mock --listen :9999 --google-credentials "$(cat </path/to/credentials/file.json)"

go run ./cmd/osquery-perf --server_url https://localhost:8080 --enroll_secret <secret> --os_templates android:2 --host_count 2 --android_pubsub_token '<token>' --android_proxy_address http://localhost:9999 --android_enterprise_id <enterprise id> --android_status_interval 30s

Testing

  • QA'd all new/changed functionality manually

Summary by CodeRabbit

  • New Features
    • Added an Android device simulation template for load testing, including Pub/Sub enrollment, periodic status reports (with optional non-compliance), and command acknowledgments.
    • Introduced an android-amapi-mock HTTP server with coordination endpoints and mocked/forwarded Android Management API routes for device, policy, enrollment token, application, web app, and enterprise flows.
    • Added Android-specific CLI flags and Android telemetry counters for enrollments, status reports, command acknowledgments, and errors.
  • Bug Fixes
    • Improved forwarded/mocked request handling with consistent JSON errors, method routing, and pagination.
    • Added a 30s HTTP client timeout and guarded error-rate calculation to avoid divide-by-zero.

@ksykulev
ksykulev requested a review from a team as a code owner July 1, 2026 00:45
Copilot AI review requested due to automatic review settings July 1, 2026 00:45

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Fleet’s existing osquery-perf load-testing harness to simulate Android MDM behavior (enrollment, periodic status reporting, and command acknowledgements) and adds a lightweight Android Management API (AMAPI) mock/proxy to support realistic policy/command flows at scale.

Changes:

  • Add Android-specific stats counters and reporting in osquery-perf.
  • Introduce an androidAgent simulator that sends Android PubSub push payloads to Fleet and polls a mock AMAPI proxy for policy/command state.
  • Add android-amapi-mock, a standalone mock/proxy server for AMAPI endpoints plus a coordination API used by osquery-perf.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
cmd/osquery-perf/osquery_perf/stats.go Adds Android counters and logs Android activity alongside existing osquery/orbit/MDM metrics.
cmd/osquery-perf/android.tmpl Placeholder template enabling --os_templates=android to work with existing template parsing.
cmd/osquery-perf/android_agent.go Implements a fake Android device loop (enroll → periodic status → command ack) using Fleet’s PubSub endpoint + mock proxy coordination.
cmd/osquery-perf/agent.go Wires Android template selection and adds Android-specific CLI flags for osquery-perf.
cmd/android-amapi-mock/middleware.go Adds middleware for forwarding non-fake-device requests to Google AMAPI when configured.
cmd/android-amapi-mock/main.go Implements the android-amapi-mock command wiring: routes, forwarding, and coordination API endpoints.
cmd/android-amapi-mock/handlers.go Adds mock handlers for device/policy/command endpoints and coordination endpoints.
cmd/android-amapi-mock/google_forwarder.go Implements Google AMAPI forwarding via the official SDK when credentials are provided.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmd/osquery-perf/osquery_perf/stats.go
Comment thread cmd/android-amapi-mock/handlers.go Outdated
Comment thread cmd/android-amapi-mock/middleware.go
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This change adds a mock Android Management API server with optional Google forwarding, in-memory device and policy state, and HTTP routes for device, policy, enrollment, application, web app, and enterprise operations. It also extends osquery-perf with an Android device simulator that registers with the mock server, sends PubSub enrollment/status/command messages to Fleet, adds Android-specific CLI flags and template support, and records Android counters in load-test stats.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description has the issue link, commands, and testing, but it omits most required checklist sections from the repository template. Add the template checklist sections and mark or remove each item, including changes files, validation, testing, migrations, config, and fleetd/orbit compatibility.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly reflects the main change: Android load testing support in osquery-perf.
Linked Issues check ✅ Passed The PR implements Android load testing infrastructure, simulated enrollment and sync flows, and command handling as requested by #26225.
Out of Scope Changes check ✅ Passed The changes stay focused on Android load testing tooling, mock AMAPI forwarding, and simulator support.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 26225-android-load-testing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (2)
cmd/osquery-perf/android_agent.go (1)

198-209: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Register/enrollment failures aren't counted in Stats.

Unlike the per-iteration status-report/poll/command-ack failures in the loop below (which call a.stats.IncrementAndroidErrors()), the initial registerWithProxy and sendEnrollment failures just log and return, leaving these failures invisible in the aggregated stats output.

🔧 Suggested fix
 	if err := a.registerWithProxy(); err != nil {
 		log.Printf("Android agent %d: failed to register with proxy: %v", a.agentIndex, err)
+		a.stats.IncrementAndroidErrors()
 		return
 	}
 
 	// Step 2: Send ENROLLMENT PubSub to Fleet
 	if err := a.sendEnrollment(); err != nil {
 		log.Printf("Android agent %d: enrollment failed: %v", a.agentIndex, err)
+		a.stats.IncrementAndroidErrors()
 		return
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/osquery-perf/android_agent.go` around lines 198 - 209, The initial
register/enrollment failures in androidAgent.runLoop are only logged, so they
never show up in aggregated error stats. Update the registerWithProxy and
sendEnrollment error paths in runLoop to also call
a.stats.IncrementAndroidErrors() before returning, matching the existing failure
handling used for the per-iteration status-report/poll/command-ack paths.
cmd/osquery-perf/agent.go (1)

4059-4062: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Validate Android flags before starting any agents, not mid-loop.

The required-flag check only fires when the loop reaches the first android.tmpl host. If --os_templates mixes android with other templates, earlier non-android hosts will already be enrolling against Fleet before this fatal error triggers, wasting the partial run.

🔧 Suggested fix: validate right after flag parsing
 	flag.Parse()
 	rand.Seed(*randSeed)
+
+	if strings.Contains(*osTemplates, "android") &&
+		(*androidPubSubToken == "" || *androidProxyAddress == "" || *androidEnterpriseID == "") {
+		log.Fatalf("Android template requires --android_pubsub_token, --android_proxy_address, and --android_enterprise_id flags")
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/osquery-perf/agent.go` around lines 4059 - 4062, Move the Android
required-flag validation out of the host-processing loop in agent.go and run it
immediately after flag parsing, before any enrollment or agent-start logic
begins. Use the existing android.tmpl check and the --android_pubsub_token,
--android_proxy_address, and --android_enterprise_id flags to fail fast up front
so mixed-template runs cannot partially enroll non-Android hosts before the
fatal error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/android-amapi-mock/google_forwarder.go`:
- Around line 104-108: ForwardDevicesList is swallowing Google API list errors
by logging and breaking out of the loop, which can make callers think the
request succeeded with a partial or empty result. Update the error path in
googleForwarder’s device-list loop to return the failure from ForwardDevicesList
instead of continuing, and make the HTTP handler that calls it propagate that
error as a failed response rather than a 200. Use the existing
ForwardDevicesList and call.Do symbols to locate the affected flow.

In `@cmd/android-amapi-mock/handlers.go`:
- Around line 229-232: The asynchronous Google forward in the handler reuses the
live request, so `ForwardPoliciesPatch` may see a canceled context or a closed
body after the handler returns. In the `google != nil &&
hasSeenRealDevice.Load()` branch, clone the incoming request before starting the
goroutine by creating a detached copy with a new context and a copied body, then
pass that cloned request into `google.ForwardPoliciesPatch` instead of `r`.
- Around line 104-107: The request body parsing in the handler that reads r.Body
and unmarshals into reqBody currently ignores malformed JSON, causing bad device
patch requests to succeed as empty patches. Update this logic to check the
json.Unmarshal error in the relevant handler in handlers.go and return a 400
response for invalid JSON instead of proceeding; keep the fix localized around
the request parsing path that handles the patch request.
- Around line 315-323: The catch-all in handleCatchAll currently returns a
successful empty JSON response for unsupported AMAPI paths, which masks missing
coverage. Update handleCatchAll to send an error status for unhandled requests
and return an error payload instead of "{}"; keep the existing logging and use
the googleForwarder context to preserve the current log message behavior.
- Around line 189-202: Validate the parsed pageToken before using it to slice
allDevices in the handler that builds resp. The current offset can become
negative from fmt.Sscanf, so add bounds checks to clamp offset to the valid
range before computing end and before evaluating allDevices[offset:end],
ensuring both offset and end are safe for slicing.
- Around line 170-184: The device list handler is mixing fake devices from all
enterprises because it always uses store.allDeviceNames() in the GET
/v1/enterprises/{eid}/devices flow. Update the handler in handlers.go so the
fake-device lookup is scoped by the requested enterprise ID (the same
r.PathValue("eid") used for enterpriseName), and only append fake devices
belonging to that enterprise before returning allDevices.

In `@cmd/osquery-perf/android_agent.go`:
- Line 268: The outbound HTTP calls in registerWithProxy, pollProxyState, and
sendPubSubMessage currently use http.Post/http.Get with http.DefaultClient and
no timeout, so they can hang indefinitely. Update these paths to use a shared
http.Client with an explicit Timeout (while preserving the existing TLS
transport customization from main in agent.go), and route the proxy
registration, polling, and Fleet PubSub requests through that client instead of
the default helpers.

---

Nitpick comments:
In `@cmd/osquery-perf/agent.go`:
- Around line 4059-4062: Move the Android required-flag validation out of the
host-processing loop in agent.go and run it immediately after flag parsing,
before any enrollment or agent-start logic begins. Use the existing android.tmpl
check and the --android_pubsub_token, --android_proxy_address, and
--android_enterprise_id flags to fail fast up front so mixed-template runs
cannot partially enroll non-Android hosts before the fatal error.

In `@cmd/osquery-perf/android_agent.go`:
- Around line 198-209: The initial register/enrollment failures in
androidAgent.runLoop are only logged, so they never show up in aggregated error
stats. Update the registerWithProxy and sendEnrollment error paths in runLoop to
also call a.stats.IncrementAndroidErrors() before returning, matching the
existing failure handling used for the per-iteration
status-report/poll/command-ack paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 420d2d0b-a623-43da-ae9c-78a99b65d9e5

📥 Commits

Reviewing files that changed from the base of the PR and between 5ada6e8 and bdb167a.

📒 Files selected for processing (8)
  • cmd/android-amapi-mock/google_forwarder.go
  • cmd/android-amapi-mock/handlers.go
  • cmd/android-amapi-mock/main.go
  • cmd/android-amapi-mock/middleware.go
  • cmd/osquery-perf/agent.go
  • cmd/osquery-perf/android.tmpl
  • cmd/osquery-perf/android_agent.go
  • cmd/osquery-perf/osquery_perf/stats.go

Comment thread cmd/android-amapi-mock/google_forwarder.go
Comment thread cmd/android-amapi-mock/handlers.go
Comment thread cmd/android-amapi-mock/handlers.go
Comment thread cmd/android-amapi-mock/handlers.go
Comment thread cmd/android-amapi-mock/handlers.go Outdated
Comment thread cmd/android-amapi-mock/handlers.go Outdated
Comment thread cmd/osquery-perf/android_agent.go
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 975 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.74%. Comparing base (422b2bb) to head (67b862e).

Files with missing lines Patch % Lines
cmd/osquery-perf/android_agent.go 0.00% 342 Missing ⚠️
cmd/android-amapi-mock/handlers.go 0.00% 272 Missing ⚠️
cmd/android-amapi-mock/google_forwarder.go 0.00% 136 Missing ⚠️
cmd/android-amapi-mock/main.go 0.00% 88 Missing ⚠️
cmd/android-amapi-mock/middleware.go 0.00% 79 Missing ⚠️
cmd/osquery-perf/osquery_perf/stats.go 0.00% 30 Missing ⚠️
cmd/osquery-perf/agent.go 0.00% 28 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #48535      +/-   ##
==========================================
- Coverage   67.97%   67.74%   -0.24%     
==========================================
  Files        3801     3777      -24     
  Lines      239965   240856     +891     
  Branches    12656    12437     -219     
==========================================
+ Hits       163123   163158      +35     
- Misses      62058    62830     +772     
- Partials    14784    14868      +84     
Flag Coverage Δ
backend 69.24% <0.00%> (-0.33%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/android-amapi-mock/handlers.go`:
- Around line 353-358: The catch-all response in handleCatchAll is building JSON
with raw r.URL.Path text, which can produce invalid output for paths needing
escaping. Update the handler to construct the error payload as a structured
value and serialize it with a JSON encoder/marshaler instead of fmt.Fprintf,
keeping the existing logging and 501 response behavior intact.
- Around line 257-277: The policy PATCH handler currently advances the version
and may forward the request even when reading or parsing the body fails, so
invalid policy updates can still appear successful. In the PATCH flow in
handlers.go, validate the request body in the policy update handler before
calling policyVersionCounter.Add, store.setPolicyVersion, or
google.ForwardPoliciesPatch; if io.ReadAll fails or the body is not valid JSON,
return an error response immediately and do not update version state. Use the
existing handler path around bodyBytes, policyVersionCounter, and
ForwardPoliciesPatch to keep the rejection logic centralized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 95457d12-5a3e-45ff-a6f4-6d2d78715edb

📥 Commits

Reviewing files that changed from the base of the PR and between bdb167a and 5228e1f.

📒 Files selected for processing (7)
  • cmd/android-amapi-mock/google_forwarder.go
  • cmd/android-amapi-mock/handlers.go
  • cmd/android-amapi-mock/main.go
  • cmd/android-amapi-mock/middleware.go
  • cmd/osquery-perf/agent.go
  • cmd/osquery-perf/android_agent.go
  • cmd/osquery-perf/osquery_perf/stats.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • cmd/osquery-perf/agent.go
  • cmd/osquery-perf/osquery_perf/stats.go
  • cmd/android-amapi-mock/google_forwarder.go
  • cmd/osquery-perf/android_agent.go
  • cmd/android-amapi-mock/main.go
  • cmd/android-amapi-mock/middleware.go

Comment thread cmd/android-amapi-mock/handlers.go Outdated
Comment thread cmd/android-amapi-mock/handlers.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cmd/android-amapi-mock/google_forwarder.go (1)

204-215: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider preserving the underlying Google error status.

writeGoogleError always emits 502 Bad Gateway, even when the underlying googleapi.Error carries a more specific code (e.g., 404 NOT_FOUND, 400 INVALID_ARGUMENT). Callers of the mock server can't distinguish "device not found on Google" from "Google API unreachable." Since this is a load-testing/mock tool the impact is limited, but extracting *googleapi.Error (via errors.As) would make error semantics more realistic for load-test scenarios that exercise error paths.

♻️ Optional refactor
 func writeGoogleError(w http.ResponseWriter, err error) {
 	log.Printf("googleForwarder: Google API error: %v", err)
 	w.Header().Set("Content-Type", "application/json")
-	w.WriteHeader(http.StatusBadGateway)
-	_ = json.NewEncoder(w).Encode(map[string]any{
-		"error": map[string]any{
-			"code":    502,
-			"message": err.Error(),
-			"status":  "BAD_GATEWAY",
-		},
-	}) //nolint:errcheck
+	code := http.StatusBadGateway
+	status := "BAD_GATEWAY"
+	var gErr *googleapi.Error
+	if errors.As(err, &gErr) {
+		code = gErr.Code
+	}
+	w.WriteHeader(code)
+	_ = json.NewEncoder(w).Encode(map[string]any{
+		"error": map[string]any{
+			"code":    code,
+			"message": err.Error(),
+			"status":  status,
+		},
+	}) //nolint:errcheck
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/android-amapi-mock/google_forwarder.go` around lines 204 - 215, Preserve
the underlying Google API status in writeGoogleError instead of always returning
a generic 502. Update the function to detect *googleapi.Error using errors.As,
and when present, use that error’s status/code and message in the JSON response
and HTTP status. Keep the existing fallback behavior for non-Google errors so
the mock server still returns 502 when no specific upstream code is available.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/android-amapi-mock/google_forwarder.go`:
- Around line 204-215: Preserve the underlying Google API status in
writeGoogleError instead of always returning a generic 502. Update the function
to detect *googleapi.Error using errors.As, and when present, use that error’s
status/code and message in the JSON response and HTTP status. Keep the existing
fallback behavior for non-Google errors so the mock server still returns 502
when no specific upstream code is available.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 602f40dc-7510-473e-b35a-1fa8d2f851a8

📥 Commits

Reviewing files that changed from the base of the PR and between 5228e1f and 597b5d3.

📒 Files selected for processing (4)
  • cmd/android-amapi-mock/google_forwarder.go
  • cmd/android-amapi-mock/handlers.go
  • cmd/android-amapi-mock/middleware.go
  • cmd/osquery-perf/android_agent.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • cmd/android-amapi-mock/middleware.go
  • cmd/osquery-perf/android_agent.go
  • cmd/android-amapi-mock/handlers.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
cmd/android-amapi-mock/handlers.go (1)

254-266: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Only half the previous fix landed — malformed JSON still advances the policy version.

The io.ReadAll error is now handled, but the body is still not validated as JSON before policyVersionCounter.Add(1) / store.setPolicyVersion(name, version) run. A malformed (but readable) body will still be accepted as a successful policy patch, silently corrupting the simulated policy-version state under load.

🐛 Proposed fix
 		if err != nil {
 			http.Error(w, "failed to read request body: "+err.Error(), http.StatusBadRequest)
 			return
 		}
+		if len(bodyBytes) > 0 && !json.Valid(bodyBytes) {
+			http.Error(w, "invalid JSON", http.StatusBadRequest)
+			return
+		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/android-amapi-mock/handlers.go` around lines 254 - 266, The policy patch
handler in handlers.go still advances state before validating the request body,
so malformed but readable JSON is treated as success. In the policy update path
inside the handler that reads r.Body, validate bodyBytes as JSON immediately
after io.ReadAll and before policyVersionCounter.Add(1) and
store.setPolicyVersion(name, version), returning an error response on parse
failure. Use the existing request-handling flow and the same handler function to
ensure only valid policy patches increment the version.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@cmd/android-amapi-mock/handlers.go`:
- Around line 254-266: The policy patch handler in handlers.go still advances
state before validating the request body, so malformed but readable JSON is
treated as success. In the policy update path inside the handler that reads
r.Body, validate bodyBytes as JSON immediately after io.ReadAll and before
policyVersionCounter.Add(1) and store.setPolicyVersion(name, version), returning
an error response on parse failure. Use the existing request-handling flow and
the same handler function to ensure only valid policy patches increment the
version.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 560e6630-9eb5-4b3e-bb44-5f80ec145ba7

📥 Commits

Reviewing files that changed from the base of the PR and between 597b5d3 and de904ef.

📒 Files selected for processing (1)
  • cmd/android-amapi-mock/handlers.go

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: test-go (fleetctl, mysql:8.0.44) / test

Failed stage: Run Go Tests [❌]

Failed test name: TestGitOpsFullGlobal

Failure summary:

The action failed because Go integration tests for fleetctl failed in TestGitOpsFullGlobal:
-
TestGitOpsFullGlobal/useDeprecatedKeys=false failed at cmd/fleetctl/fleetctl/gitops_test.go:2244
(via cmd/fleetctl/fleetctl/testing_utils_test.go:20) with an unexpected API error while "applying
custom settings".
- TestGitOpsFullGlobal/useDeprecatedKeys=true failed at the same locations for the
same reason.
The underlying error was a 422 response from POST /api/latest/fleet/mdm/profiles/batch:

- Validation Failed: cannot set custom settings: Windows MDM isn't turned on.
This caused make
.run-go-tests (Makefile target) to exit non-zero, failing the job.

Relevant error logs:
1:  Runner name: 'ubuntu-8core-1000965524'
2:  Runner group name: 'default larger runners'
...

1256:  �[36;1mattempt=1�[0m
1257:  �[36;1m�[0m
1258:  �[36;1mwhile [ $attempt -le $max_attempts ]; do�[0m
1259:  �[36;1m  echo "Attempt $attempt of $max_attempts"�[0m
1260:  �[36;1m�[0m
1261:  �[36;1m  # Try to connect to MySQL�[0m
1262:  �[36;1m  if wait_for_mysql "mysql_test"; then�[0m
1263:  �[36;1m    # If MySQL is ready, try to connect to MySQL replica�[0m
1264:  �[36;1m    if wait_for_mysql "mysql_replica_test"; then�[0m
1265:  �[36;1m      # Both are ready, we're done�[0m
1266:  �[36;1m      echo "All MySQL connections successful"�[0m
1267:  �[36;1m      exit 0�[0m
1268:  �[36;1m    fi�[0m
1269:  �[36;1m  fi�[0m
1270:  �[36;1m�[0m
1271:  �[36;1m  # If we get here, at least one connection failed�[0m
1272:  �[36;1m  echo "Failed to connect to MySQL on attempt $attempt"�[0m
1273:  �[36;1m�[0m
1274:  �[36;1m  if [ $attempt -lt $max_attempts ]; then�[0m
1275:  �[36;1m    echo "Restarting containers and trying again..."�[0m
1276:  �[36;1m    restart_containers�[0m
1277:  �[36;1m  else�[0m
1278:  �[36;1m    echo "Maximum attempts reached. Failing the job."�[0m
1279:  �[36;1m    exit 1�[0m
...

1435:  make .run-go-tests PKG_TO_TEST="./cmd/fleetctl/..."
1436:  make[1]: Entering directory '/home/runner/work/fleet/fleet'
1437:  Running Go tests with gotestsum:
1438:  gotestsum --format=testdox --jsonfile=/tmp/test-output.json -- -tags full,fts5,netgo -run=  -v -race=false -timeout=20m  -parallel 8 -coverprofile=coverage.txt -covermode=atomic -coverpkg=github.com/fleetdm/fleet/v4/... ././cmd/fleetctl/... 
1439:  github.com/fleetdm/fleet/v4/cmd/fleetctl:
1440:  github.com/fleetdm/fleet/v4/cmd/fleetctl/fleetctl/testing_utils:
1441:  github.com/fleetdm/fleet/v4/cmd/fleetctl/fleetctl/goquerycmd:
1442:  github.com/fleetdm/fleet/v4/cmd/fleetctl/integrationtest:
1443:  github.com/fleetdm/fleet/v4/cmd/fleetctl/fleetctl/fleetctltest:
1444:  github.com/fleetdm/fleet/v4/cmd/fleetctl/integrationtest/package:
1445:  �[32m✓�[0m Package (3.85s)
1446:  �[32m✓�[0m Package - -use-sytem-configuration can't be used on installers that aren't pkg (0.00s)
1447:  �[32m✓�[0m Package deb (1.73s)
1448:  github.com/fleetdm/fleet/v4/cmd/fleetctl/integrationtest/preview:
1449:  �[32m✓�[0m Integrations preview (53.67s)
1450:  �[32m✓�[0m Preview fails on invalid license key (0.00s)
1451:  github.com/fleetdm/fleet/v4/cmd/fleetctl/integrationtest/vuln:
...

1558:  �[32m✓�[0m Apply specs deprecated keys app config windows updates.grace period days not a number (0.44s)
1559:  �[32m✓�[0m Apply specs deprecated keys app config windows updates.grace period days out of range (0.45s)
1560:  �[32m✓�[0m Apply specs deprecated keys config with FIM values for agent options (#869 9) (0.47s)
1561:  �[32m✓�[0m Apply specs deprecated keys config with blank required org name (0.62s)
1562:  �[32m✓�[0m Apply specs deprecated keys config with blank required server url (0.67s)
1563:  �[32m✓�[0m Apply specs deprecated keys config with invalid agent options command-line flags (0.45s)
1564:  �[32m✓�[0m Apply specs deprecated keys config with invalid agent options data type in dry-run (0.43s)
1565:  �[32m✓�[0m Apply specs deprecated keys config with invalid agent options data type with force (0.41s)
1566:  �[32m✓�[0m Apply specs deprecated keys config with invalid agent options in dry-run (0.52s)
1567:  �[32m✓�[0m Apply specs deprecated keys config with invalid key type (0.43s)
1568:  �[32m✓�[0m Apply specs deprecated keys config with invalid value for agent options command-line flags (0.47s)
1569:  �[32m✓�[0m Apply specs deprecated keys config with unknown key (0.73s)
1570:  �[32m✓�[0m Apply specs deprecated keys config with valid agent options command-line flags (0.39s)
1571:  �[32m✓�[0m Apply specs deprecated keys dry-run set with unsupported spec (0.57s)
1572:  �[32m✓�[0m Apply specs deprecated keys dry-run set with various specs, appconfig warning for legacy (0.49s)
1573:  �[32m✓�[0m Apply specs deprecated keys dry-run set with various specs, no errors (0.53s)
1574:  �[32m✓�[0m Apply specs deprecated keys empty config (0.53s)
...

1577:  �[32m✓�[0m Apply specs deprecated keys invalid agent options dry-run (0.53s)
1578:  �[32m✓�[0m Apply specs deprecated keys invalid agent options field type (0.52s)
1579:  �[32m✓�[0m Apply specs deprecated keys invalid agent options field type in overrides (0.66s)
1580:  �[32m✓�[0m Apply specs deprecated keys invalid agent options for existing team (0.44s)
1581:  �[32m✓�[0m Apply specs deprecated keys invalid agent options for new team (0.56s)
1582:  �[32m✓�[0m Apply specs deprecated keys invalid agent options force (0.46s)
1583:  �[32m✓�[0m Apply specs deprecated keys invalid known key's value type for team cannot be forced (0.57s)
1584:  �[32m✓�[0m Apply specs deprecated keys invalid team agent options command-line flag (0.52s)
1585:  �[32m✓�[0m Apply specs deprecated keys invalid top-level key for team (0.44s)
1586:  �[32m✓�[0m Apply specs deprecated keys macos updates deadline set but minimum version empty (0.63s)
1587:  �[32m✓�[0m Apply specs deprecated keys macos updates minimum version set but deadline empty (0.43s)
1588:  �[32m✓�[0m Apply specs deprecated keys macos updates.deadline with incomplete date (0.53s)
1589:  �[32m✓�[0m Apply specs deprecated keys macos updates.deadline with invalid date (0.38s)
1590:  �[32m✓�[0m Apply specs deprecated keys macos updates.deadline with timestamp (0.41s)
1591:  �[32m✓�[0m Apply specs deprecated keys macos updates.minimum version with build version (0.44s)
1592:  �[32m✓�[0m Apply specs deprecated keys missing required failing policies destination url (0.46s)
1593:  �[32m✓�[0m Apply specs deprecated keys missing required host status days count (0.41s)
...

1601:  �[32m✓�[0m Apply specs deprecated keys team config macos settings.enable disk encryption true (0.50s)
1602:  �[32m✓�[0m Apply specs deprecated keys team config macos settings.enable disk encryption with invalid value type (0.49s)
1603:  �[32m✓�[0m Apply specs deprecated keys team config macos settings.enable disk encryption without a value (0.40s)
1604:  �[32m✓�[0m Apply specs deprecated keys unknown key for team can be forced (0.62s)
1605:  �[32m✓�[0m Apply specs deprecated keys valid team agent options command-line flag (0.40s)
1606:  �[32m✓�[0m Apply specs deprecated keys windows updates unset valid (0.46s)
1607:  �[32m✓�[0m Apply specs deprecated keys windows updates valid (0.43s)
1608:  �[32m✓�[0m Apply specs deprecated keys windows updates.deadline days but grace period empty (0.60s)
1609:  �[32m✓�[0m Apply specs deprecated keys windows updates.deadline days not a number (0.48s)
1610:  �[32m✓�[0m Apply specs deprecated keys windows updates.deadline days out of range (0.45s)
1611:  �[32m✓�[0m Apply specs deprecated keys windows updates.grace period days but deadline empty (0.41s)
1612:  �[32m✓�[0m Apply specs deprecated keys windows updates.grace period days not a number (0.48s)
1613:  �[32m✓�[0m Apply specs deprecated keys windows updates.grace period days out of range (0.51s)
1614:  �[32m✓�[0m Apply specs dry-run set with unsupported spec (0.47s)
1615:  �[32m✓�[0m Apply specs dry-run set with various specs, appconfig warning for legacy (0.45s)
1616:  �[32m✓�[0m Apply specs dry-run set with various specs, no errors (0.47s)
1617:  �[32m✓�[0m Apply specs empty config (0.44s)
...

1620:  �[32m✓�[0m Apply specs invalid agent options dry-run (0.53s)
1621:  �[32m✓�[0m Apply specs invalid agent options field type (0.41s)
1622:  �[32m✓�[0m Apply specs invalid agent options field type in overrides (0.41s)
1623:  �[32m✓�[0m Apply specs invalid agent options for existing team (0.41s)
1624:  �[32m✓�[0m Apply specs invalid agent options for new team (0.47s)
1625:  �[32m✓�[0m Apply specs invalid agent options force (0.44s)
1626:  �[32m✓�[0m Apply specs invalid known key's value type for team cannot be forced (0.45s)
1627:  �[32m✓�[0m Apply specs invalid team agent options command-line flag (0.41s)
1628:  �[32m✓�[0m Apply specs invalid top-level key for team (0.41s)
1629:  �[32m✓�[0m Apply specs macos updates deadline set but minimum version empty (0.44s)
1630:  �[32m✓�[0m Apply specs macos updates minimum version set but deadline empty (0.57s)
1631:  �[32m✓�[0m Apply specs macos updates.deadline with incomplete date (0.50s)
1632:  �[32m✓�[0m Apply specs macos updates.deadline with invalid date (0.38s)
1633:  �[32m✓�[0m Apply specs macos updates.deadline with timestamp (0.47s)
1634:  �[32m✓�[0m Apply specs macos updates.minimum version with build version (0.39s)
1635:  �[32m✓�[0m Apply specs missing required failing policies destination url (0.50s)
1636:  �[32m✓�[0m Apply specs missing required host status days count (0.42s)
...

1655:  �[32m✓�[0m Apply specs windows updates.grace period days not a number (0.43s)
1656:  �[32m✓�[0m Apply specs windows updates.grace period days out of range (0.39s)
1657:  �[32m✓�[0m Apply team specs (0.62s)
1658:  �[32m✓�[0m Apply user roles (0.52s)
1659:  �[32m✓�[0m Apply user roles deprecated (0.55s)
1660:  �[32m✓�[0m Apply windows updates (0.48s)
1661:  �[32m✓�[0m Apply windows updates field omitted (0.00s)
1662:  �[32m✓�[0m Apply windows updates with null values (0.00s)
1663:  �[32m✓�[0m Apply windows updates with values (0.00s)
1664:  �[32m✓�[0m Can apply intervals in nanoseconds (0.41s)
1665:  �[32m✓�[0m Can apply intervals using durations (0.49s)
1666:  �[32m✓�[0m Clean status code err (0.00s)
1667:  �[32m✓�[0m Clean status code err bare wrapped status code err (0.00s)
1668:  �[32m✓�[0m Clean status code err nil (0.00s)
1669:  �[32m✓�[0m Clean status code err outer-wrapped status code err (0.00s)
1670:  �[32m✓�[0m Clean status code err plain error untouched (0.00s)
1671:  �[32m✓�[0m Compute label changes (0.00s)
...

1727:  �[32m✓�[0m Filename functions (0.00s)
1728:  �[32m✓�[0m Filename functions outfile name builds a file name using the name provided + current time (0.00s)
1729:  �[32m✓�[0m Filename functions outfile name with ext builds a file name using the name and extension provided + current time (0.00s)
1730:  �[32m✓�[0m FleetctlUpgradePacks empty packs (0.48s)
1731:  �[32m✓�[0m FleetctlUpgradePacks no pack (0.42s)
1732:  �[32m✓�[0m FleetctlUpgradePacks non empty (0.42s)
1733:  �[32m✓�[0m FleetctlUpgradePacks not admin (0.42s)
1734:  �[32m✓�[0m Format XML (0.00s)
1735:  �[32m✓�[0m Format XML XML with attributes (0.00s)
1736:  �[32m✓�[0m Format XML basic XML (0.00s)
1737:  �[32m✓�[0m Format XML empty XML (0.00s)
1738:  �[32m✓�[0m Format XML invalid XML (0.00s)
1739:  �[32m✓�[0m Format XML nested XML (0.00s)
1740:  �[32m✓�[0m Generate MDM apple (0.81s)
1741:  �[32m✓�[0m Generate MDM apple BM (0.53s)
1742:  �[32m✓�[0m Generate MDM apple CSR API call fails (0.44s)
1743:  �[32m✓�[0m Generate MDM apple successful run (0.37s)
1744:  �[32m✓�[0m Generate MDMVPP tokens (0.00s)
1745:  �[32m✓�[0m Generate MDMVPP tokens get VPP tokens error (0.00s)
1746:  �[32m✓�[0m Generate MDMVPP tokens multiple tokens with different teams (0.00s)
...

1764:  �[32m✓�[0m Generate org settings masked google workspace api key (0.00s)
1765:  �[32m✓�[0m Generate policies (0.00s)
1766:  �[32m✓�[0m Generate policies patch policy orphaned from fleet maintained app (0.00s)
1767:  �[32m✓�[0m Generate queries (0.00s)
1768:  �[32m✓�[0m Generate software (0.00s)
1769:  �[32m✓�[0m Generate software auto update schedule (0.00s)
1770:  �[32m✓�[0m Generate software script packages (0.00s)
1771:  �[32m✓�[0m Generate team settings (0.00s)
1772:  �[32m✓�[0m Generate team settings insecure (0.00s)
1773:  �[32m✓�[0m Generated org settings no SSO (0.00s)
1774:  �[32m✓�[0m Generated org settings okta conditional access not included (0.00s)
1775:  �[32m✓�[0m Get MDM command results (0.54s)
1776:  �[32m✓�[0m Get MDM command results command flag required (0.00s)
1777:  �[32m✓�[0m Get MDM command results command not found (0.01s)
1778:  �[32m✓�[0m Get MDM command results command results empty (0.01s)
1779:  �[32m✓�[0m Get MDM command results command results error (0.01s)
1780:  �[32m✓�[0m Get MDM command results darwin command results (0.00s)
1781:  �[32m✓�[0m Get MDM command results host specific results (0.00s)
1782:  �[32m✓�[0m Get MDM command results windows command results (0.00s)
1783:  �[32m✓�[0m Get MDM commands (0.35s)
1784:  �[32m✓�[0m Get apple BM (1.78s)
1785:  �[32m✓�[0m Get apple BM free license (0.38s)
1786:  �[32m✓�[0m Get apple BM premium license, multiple tokens (0.55s)
1787:  �[32m✓�[0m Get apple BM premium license, no token (0.44s)
1788:  �[32m✓�[0m Get apple BM premium license, single token (0.40s)
1789:  �[32m✓�[0m Get apple MDM (0.48s)
1790:  �[32m✓�[0m Get carve (0.36s)
1791:  �[32m✓�[0m Get carve with error (0.39s)
1792:  �[32m✓�[0m Get carves (0.37s)
...

1806:  �[32m✓�[0m Get hosts MDM get hosts - -mdm - -mdm-pending - (0.00s)
1807:  �[32m✓�[0m Get hosts MDM get hosts - -mdm-pending - -yaml - expected list hosts yaml.yml (0.01s)
1808:  �[32m✓�[0m Get hosts get hosts - -json - -remove-deprecated-keys (0.00s)
1809:  �[32m✓�[0m Get hosts get hosts - -json - expected list hosts json.json (0.00s)
1810:  �[32m✓�[0m Get hosts get hosts - -json test host - expected host detail response json.json (0.00s)
1811:  �[32m✓�[0m Get hosts get hosts - -yaml - expected list hosts yaml.yml (0.00s)
1812:  �[32m✓�[0m Get hosts get hosts - -yaml test host - expected host detail response yaml.yml (0.00s)
1813:  �[32m✓�[0m Get label (0.41s)
1814:  �[32m✓�[0m Get label usage include and exclude allowed (0.00s)
1815:  �[32m✓�[0m Get label usage include and exclude allowed macos (0.00s)
1816:  �[32m✓�[0m Get label usage include and exclude allowed macos# 01 (0.00s)
1817:  �[32m✓�[0m Get label usage include and exclude allowed macos# 02 (0.00s)
1818:  �[32m✓�[0m Get label usage include and exclude allowed windows (0.00s)
1819:  �[32m✓�[0m Get label usage include and exclude allowed windows# 01 (0.00s)
1820:  �[32m✓�[0m Get label usage include and exclude allowed windows# 02 (0.00s)
1821:  �[32m✓�[0m Get label usage include exclude overlap error (0.00s)
1822:  �[32m✓�[0m Get label usage include exclude overlap error macos (0.00s)
1823:  �[32m✓�[0m Get label usage include exclude overlap error macos# 01 (0.00s)
1824:  �[32m✓�[0m Get label usage include exclude overlap error macos# 02 (0.00s)
1825:  �[32m✓�[0m Get label usage include exclude overlap error windows (0.00s)
1826:  �[32m✓�[0m Get label usage include exclude overlap error windows# 01 (0.00s)
1827:  �[32m✓�[0m Get label usage include exclude overlap error windows# 02 (0.00s)
1828:  �[32m✓�[0m Get label usage multiple label keys error (0.00s)
1829:  �[32m✓�[0m Get label usage multiple label keys error macos (0.00s)
1830:  �[32m✓�[0m Get label usage multiple label keys error windows (0.00s)
1831:  �[32m✓�[0m Get label usage policy scopes (0.00s)
...

1847:  �[32m✓�[0m Get queries as observer team observer (0.01s)
1848:  �[32m✓�[0m Get query (0.47s)
1849:  �[32m✓�[0m Get query labels include all (0.57s)
1850:  �[32m✓�[0m Get reports labels include all (0.42s)
1851:  �[32m✓�[0m Get software titles (0.45s)
1852:  �[32m✓�[0m Get software versions (0.49s)
1853:  �[32m✓�[0m Get teams (0.94s)
1854:  �[32m✓�[0m Get teams YAML and apply (0.38s)
1855:  �[32m✓�[0m Get teams by name (0.44s)
1856:  �[32m✓�[0m Get teams expired license (0.50s)
1857:  �[32m✓�[0m Get teams not expired license (0.45s)
1858:  �[32m✓�[0m Get teams software from source of truth (0.46s)
1859:  �[32m✓�[0m Get user roles (0.45s)
1860:  �[32m✓�[0m Git ops ABM (6.40s)
1861:  �[32m✓�[0m Git ops ABM backwards compat (0.51s)
1862:  �[32m✓�[0m Git ops ABM both keys errors (0.68s)
1863:  �[32m✓�[0m Git ops ABM deprecated config with two tokens in the db fails (0.61s)
1864:  �[32m✓�[0m Git ops ABM new key all valid (0.74s)
1865:  �[32m✓�[0m Git ops ABM new key multiple elements (0.70s)
1866:  �[32m✓�[0m Git ops ABM no team is supported (0.54s)
1867:  �[32m✓�[0m Git ops ABM non existent org name fails (0.53s)
1868:  �[32m✓�[0m Git ops ABM not provided teams defaults to no team (0.67s)
1869:  �[32m✓�[0m Git ops ABM renamed new key all valid (0.86s)
1870:  �[32m✓�[0m Git ops ABM using an undefined team errors (0.56s)
1871:  �[32m✓�[0m Git ops EULA setting (4.87s)
...

1874:  �[32m✓�[0m Git ops EULA setting not a PDF file (0.49s)
1875:  �[32m✓�[0m Git ops EULA setting relative path to working dir to pdf file (no existing EULA uploaded) (0.77s)
1876:  �[32m✓�[0m Git ops EULA setting relative path to yaml file to pdf file (no existing EULA uploaded) (0.62s)
1877:  �[32m✓�[0m Git ops EULA setting uploading the same EULA again (0.59s)
1878:  �[32m✓�[0m Git ops EULA setting valid new pdf file (different EULA already uploaded) (0.44s)
1879:  �[32m✓�[0m Git ops EULA setting valid pdf file (no existing EULA uploaded) (0.83s)
1880:  �[32m✓�[0m Git ops MDM auth settings (0.58s)
1881:  �[32m✓�[0m Git ops SMTP settings (0.48s)
1882:  �[32m✓�[0m Git ops SSO server URL (0.67s)
1883:  �[32m✓�[0m Git ops SSO settings (0.46s)
1884:  �[32m✓�[0m Git ops android certificates add (0.48s)
1885:  �[32m✓�[0m Git ops android certificates change (0.55s)
1886:  �[32m✓�[0m Git ops android certificates delete all (0.64s)
1887:  �[32m✓�[0m Git ops android certificates delete one (0.47s)
1888:  �[32m✓�[0m Git ops app store app auto update (0.50s)
1889:  �[32m✓�[0m Git ops app store app auto update invalid auto-update window triggers error and does not call update software title auto update config (0.01s)
1890:  �[32m✓�[0m Git ops app store app auto update no auto update settings and no existing schedule does not call update software title auto update config (0.02s)
1891:  �[32m✓�[0m Git ops app store app auto update update software title auto update config is applied for i OS VPP apps (0.02s)
1892:  �[32m✓�[0m Git ops app store app auto update update software title auto update config is not called when no VPP apps provided (0.02s)
1893:  �[32m✓�[0m Git ops apple OS updates (0.63s)
1894:  �[32m✓�[0m Git ops apple OS updates ios updates (0.01s)
1895:  �[32m✓�[0m Git ops apple OS updates ios updates os updated when existing OS update declaration (0.01s)
1896:  �[32m✓�[0m Git ops apple OS updates ipados updates (0.01s)
1897:  �[32m✓�[0m Git ops apple OS updates ipados updates os updated when existing OS update declaration (0.01s)
1898:  �[32m✓�[0m Git ops apple OS updates macos updates (0.01s)
1899:  �[32m✓�[0m Git ops apple OS updates macos updates os updated when existing OS update declaration (0.01s)
1900:  �[32m✓�[0m Git ops basic global and no team (0.56s)
1901:  �[32m✓�[0m Git ops basic global and no team basic global and no-team.yml (0.06s)
1902:  �[32m✓�[0m Git ops basic global and no team both global and no-team.yml define controls -- should fail (0.01s)
1903:  �[32m✓�[0m Git ops basic global and no team controls only defined in no-team.yml (0.05s)
1904:  �[32m✓�[0m Git ops basic global and no team global DOES NOT define controls -- should fail (0.01s)
1905:  �[32m✓�[0m Git ops basic global and no team global and no-team.yml DO NOT define controls -- should fail (0.01s)
1906:  �[32m✓�[0m Git ops basic global and no team global defines software -- should fail (0.01s)
1907:  �[32m✓�[0m Git ops basic global and no team no-team provided without global -- should fail (0.01s)
1908:  �[32m✓�[0m Git ops basic global and no team no-team.yml defines policy with calendar events enabled -- should fail (0.01s)
1909:  �[32m✓�[0m Git ops basic global and no team unassigned provided without global -- should fail (0.01s)
1910:  �[32m✓�[0m Git ops basic global and team (0.66s)
...

1916:  �[32m✓�[0m Git ops custom settings global macos windows custom settings valid.yml (0.46s)
1917:  �[32m✓�[0m Git ops custom settings global windows custom settings invalid label mix 2 .yml (0.49s)
1918:  �[32m✓�[0m Git ops custom settings global windows custom settings invalid label mix.yml (0.43s)
1919:  �[32m✓�[0m Git ops custom settings global windows custom settings unknown label.yml (0.50s)
1920:  �[32m✓�[0m Git ops custom settings team macos custom settings valid deprecated.yml (0.52s)
1921:  �[32m✓�[0m Git ops custom settings team macos windows custom settings invalid labels mix 2 .yml (0.69s)
1922:  �[32m✓�[0m Git ops custom settings team macos windows custom settings invalid labels mix.yml (0.49s)
1923:  �[32m✓�[0m Git ops custom settings team macos windows custom settings unknown label.yml (0.56s)
1924:  �[32m✓�[0m Git ops custom settings team macos windows custom settings valid.yml (0.51s)
1925:  �[32m✓�[0m Git ops dry run rejects invalid label platform (0.46s)
1926:  �[32m✓�[0m Git ops exception enforcement (0.43s)
1927:  �[32m✓�[0m Git ops exception enforcement free tier (0.51s)
1928:  �[32m✓�[0m Git ops exceptions preserve omitted keys (0.42s)
1929:  �[32m✓�[0m Git ops features (0.55s)
1930:  �[32m✓�[0m Git ops filename validation (0.00s)
1931:  �[32m✓�[0m Git ops fleet failing policies webhook policy IDs (0.59s)
1932:  �[32m✓�[0m Git ops fleet webhooks and tickets enabled (0.61s)
...

2089:  �[32m✓�[0m New basic file structure has expected files (0.00s)
2090:  �[32m✓�[0m New basic file structure replaces and escapes org name template var (0.00s)
2091:  �[32m✓�[0m New basic file structure strips .template. from output filenames (0.00s)
2092:  �[32m✓�[0m New dir flag (0.01s)
2093:  �[32m✓�[0m New existing dir with force (0.01s)
2094:  �[32m✓�[0m New existing dir without force (0.00s)
2095:  �[32m✓�[0m New org name YAML quoting (0.01s)
2096:  �[32m✓�[0m New org name validation (0.01s)
2097:  �[32m✓�[0m New org name validation at max length (0.01s)
2098:  �[32m✓�[0m New org name validation control characters stripped (0.01s)
2099:  �[32m✓�[0m New org name validation only control characters (0.00s)
2100:  �[32m✓�[0m New org name validation only whitespace (0.00s)
2101:  �[32m✓�[0m New org name validation too long (0.00s)
2102:  �[32m✓�[0m New output messages (0.01s)
2103:  �[32m✓�[0m New template stripping (0.01s)
2104:  �[32m✓�[0m Print auth error (0.47s)
2105:  �[32m✓�[0m Print auth error SSO disabled shows default login message (0.00s)
2106:  �[32m✓�[0m Print auth error SSO enabled shows SSO instructions (0.00s)
2107:  �[32m✓�[0m Render template (0.00s)
...

2127:  �[32m✓�[0m Run api command get scripts full path missing (0.00s)
2128:  �[32m✓�[0m Run api command get scripts team (0.00s)
2129:  �[32m✓�[0m Run api command get scripts team no cache (0.00s)
2130:  �[32m✓�[0m Run api command get typo (0.00s)
2131:  �[32m✓�[0m Run api command upload script (0.00s)
2132:  �[32m✓�[0m Run script command (0.57s)
2133:  �[32m✓�[0m Run script command disabled scripts globally (0.00s)
2134:  �[32m✓�[0m Run script command host not found (0.01s)
2135:  �[32m✓�[0m Run script command invalid file type (0.00s)
2136:  �[32m✓�[0m Run script command invalid hashbang (0.00s)
2137:  �[32m✓�[0m Run script command invalid utf 8 (0.00s)
2138:  �[32m✓�[0m Run script command missing one of script-path and script-nqme (0.00s)
2139:  �[32m✓�[0m Run script command output truncated (0.01s)
2140:  �[32m✓�[0m Run script command posix shell hashbang (0.01s)
2141:  �[32m✓�[0m Run script command script empty (0.01s)
2142:  �[32m✓�[0m Run script command script failed (0.01s)
2143:  �[32m✓�[0m Run script command script killed (0.01s)
...

2198:  �[32m✓�[0m Validate git ops group EUA global-only run degrades id p but the team's in-run file disables EU A: accepted (0.00s)
2199:  �[32m✓�[0m Validate git ops group EUA global-only run degrades id p while a stored team keeps EUA on: rejected (#4337 1) (0.00s)
2200:  �[32m✓�[0m Validate git ops group EUA no EUA enabled anywhere is accepted (0.00s)
2201:  �[32m✓�[0m Validate git ops group EUA team enables EU A, global file adds complete id P: accepted (0.00s)
2202:  �[32m✓�[0m Validate git ops group EUA team enables EU A, global file adds id p missing entity id: rejected (0.00s)
2203:  �[32m✓�[0m Validate git ops group EUA team enables EU A, global file omits id P, stored has id P: rejected (overwrite clears) (0.00s)
2204:  �[32m✓�[0m Validate git ops group EUA team enables EU A, stored has id P, no global file: accepted (0.00s)
2205:  �[32m✓�[0m Validate git ops group EUA team enables EU A, stored has no id P, no global file: rejected (0.00s)
2206:  github.com/fleetdm/fleet/v4/cmd/fleetctl/integrationtest/gitops:
2207:  �[32m✓�[0m Git ops VPP (5.58s)
2208:  �[32m✓�[0m Git ops VPP all fleets is supported (0.65s)
2209:  �[32m✓�[0m Git ops VPP all teams is supported (0.61s)
2210:  �[32m✓�[0m Git ops VPP new key all valid (0.71s)
2211:  �[32m✓�[0m Git ops VPP new key multiple elements (0.71s)
2212:  �[32m✓�[0m Git ops VPP no team is supported (0.63s)
2213:  �[32m✓�[0m Git ops VPP non existent location fails (0.61s)
2214:  �[32m✓�[0m Git ops VPP not provided teams defaults to no team (0.84s)
2215:  �[32m✓�[0m Git ops VPP using an undefined team errors (0.81s)
2216:  �[32m✓�[0m Git ops existing team VPP apps with missing team (0.81s)
...

2309:  �[32m✓�[0m Git ops team software installers team software installer with display name.yml (1.49s)
2310:  �[32m✓�[0m Integrations enterprise gitops (317.71s)
2311:  �[32m✓�[0m Integrations enterprise gitops test CA integrations (3.92s)
2312:  �[32m✓�[0m Integrations enterprise gitops test FMA labels include all (6.02s)
2313:  �[32m✓�[0m Integrations enterprise gitops test IPA software installers (10.46s)
2314:  �[32m✓�[0m Integrations enterprise gitops test JSON configuration profile escaping (1.29s)
2315:  �[32m✓�[0m Integrations enterprise gitops test add manual labels (1.55s)
2316:  �[32m✓�[0m Integrations enterprise gitops test configuration profile escaping (1.35s)
2317:  �[32m✓�[0m Integrations enterprise gitops test delete CA with certificate templates (6.02s)
2318:  �[32m✓�[0m Integrations enterprise gitops test delete mac OS setup (4.95s)
2319:  �[32m✓�[0m Integrations enterprise gitops test deleting no team YAML (2.66s)
2320:  �[32m✓�[0m Integrations enterprise gitops test disallow software setup experience (123.79s)
2321:  �[32m✓�[0m Integrations enterprise gitops test disallow software setup experience all VPP with setup experience (1.27s)
2322:  �[32m✓�[0m Integrations enterprise gitops test disallow software setup experience no team VPP (1.14s)
2323:  �[32m✓�[0m Integrations enterprise gitops test disallow software setup experience no team installers (60.54s)
2324:  �[32m✓�[0m Integrations enterprise gitops test disallow software setup experience packages fail (60.66s)
2325:  �[32m✓�[0m Integrations enterprise gitops test dry run mac OS setup script with manual agent install conflict (0.43s)
...

2355:  �[32m✓�[0m Integrations enterprise gitops test omitted top level keys global (2.47s)
2356:  �[32m✓�[0m Integrations enterprise gitops test remove custom settings from default YAML (2.56s)
2357:  �[32m✓�[0m Integrations enterprise gitops test special case teams VPP apps (3.79s)
2358:  �[32m✓�[0m Integrations enterprise gitops test special case teams VPP apps all teams (2.39s)
2359:  �[32m✓�[0m Integrations enterprise gitops test special case teams VPP apps no team (1.23s)
2360:  �[32m✓�[0m Integrations enterprise gitops test unset configuration profile labels (4.91s)
2361:  �[32m✓�[0m Integrations enterprise gitops test unset software installer labels (12.35s)
2362:  �[32m✓�[0m Integrations enterprise starter library (4.98s)
2363:  �[32m✓�[0m Integrations enterprise starter library test apply starter library premium (3.54s)
2364:  �[32m✓�[0m Integrations gitops (2.45s)
2365:  �[32m✓�[0m Integrations gitops test fleet gitops (0.67s)
2366:  �[32m✓�[0m Integrations gitops test fleet gitops DDM fleet vars requires premium (0.12s)
2367:  �[32m✓�[0m Integrations gitops test fleet gitops with fleet secrets (0.23s)
2368:  �[32m✓�[0m Integrations starter library (1.61s)
2369:  �[32m✓�[0m Integrations starter library test apply starter library free (0.19s)
2370:  === �[31mFailed�[0m
2371:  === �[31mFAIL�[0m: cmd/fleetctl/fleetctl TestGitOpsFullGlobal/useDeprecatedKeys=false (0.04s)
2372:  time=level=INFO msg="request error" path=/api/latest/fleet/setup_experience/eula/metadata took=126.942µs uuid=dd97be92-b097-4108-af7d-867e514097e7 err="not found"
2373:  [-] would've deleted report Query to delete
2374:  time=level=INFO msg="request error" path=/api/latest/fleet/setup_experience/eula/metadata took=156.937µs uuid=1d06c5eb-d64d-4d0e-bde8-7e67971846da err="not found"
2375:  testing_utils_test.go:20: 
2376:  Error Trace:	/home/runner/work/fleet/fleet/cmd/fleetctl/fleetctl/testing_utils_test.go:20
2377:  /home/runner/work/fleet/fleet/cmd/fleetctl/fleetctl/gitops_test.go:2244
2378:  Error:      	Received unexpected error:
2379:  applying custom settings: POST /api/latest/fleet/mdm/profiles/batch received status 422 Validation Failed: cannot set custom settings: Windows MDM isn't turned on. For more information about setting up MDM, please visit https://fleetdm.com/learn-more-about/windows-mdm (API time: 1ms)
2380:  Test:       	TestGitOpsFullGlobal/useDeprecatedKeys=false
2381:  --- FAIL: TestGitOpsFullGlobal/useDeprecatedKeys=false (0.04s)
2382:  === �[31mFAIL�[0m: cmd/fleetctl/fleetctl TestGitOpsFullGlobal/useDeprecatedKeys=true (0.04s)
2383:  time=level=INFO msg="request error" path=/api/latest/fleet/setup_experience/eula/metadata took=117.778µs uuid=5c1ff1ef-dca8-4b91-aaf7-a29af75873b9 err="not found"
2384:  [-] would've deleted report Query to delete
2385:  time=level=INFO msg="request error" path=/api/latest/fleet/setup_experience/eula/metadata took=154.464µs uuid=a4f4d1d5-597c-48f2-b016-1441b86a2dfa err="not found"
2386:  testing_utils_test.go:20: 
2387:  Error Trace:	/home/runner/work/fleet/fleet/cmd/fleetctl/fleetctl/testing_utils_test.go:20
2388:  /home/runner/work/fleet/fleet/cmd/fleetctl/fleetctl/gitops_test.go:2244
2389:  Error:      	Received unexpected error:
2390:  applying custom settings: POST /api/latest/fleet/mdm/profiles/batch received status 422 Validation Failed: cannot set custom settings: Windows MDM isn't turned on. For more information about setting up MDM, please visit https://fleetdm.com/learn-more-about/windows-mdm (API time: 1ms)
2391:  Test:       	TestGitOpsFullGlobal/useDeprecatedKeys=true
2392:  --- FAIL: TestGitOpsFullGlobal/useDeprecatedKeys=true (0.04s)
2393:  === �[31mFAIL�[0m: cmd/fleetctl/fleetctl TestGitOpsFullGlobal (0.61s)
2394:  DONE 921 tests, 3 failures in 653.682s
2395:  make[1]: *** [Makefile:302: .run-go-tests] Error 1
2396:  make[1]: Leaving directory '/home/runner/work/fleet/fleet'
2397:  make: *** [Makefile:417: test-go] Error 2
2398:  ##[error]Process completed with exit code 2.
2399:  Node 20 is being deprecated. This workflow is running with Node 24 by default. If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
2400:  ##[group]Run actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a
2401:  with:
2402:  name: fleetctl-mysql8.0.44-coverage
2403:  path: ./coverage.txt
2404:  if-no-files-found: error
2405:  compression-level: 6
...

2408:  RACE_ENABLED: false
2409:  GO_TEST_TIMEOUT: 20m
2410:  DOCKER_COMMAND: docker compose -f docker-compose.yml -f docker-compose-redis-cluster.yml up -d mysql_test mysql_replica_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup s3 saml_idp mailhog mailpit smtp4dev_test
2411:  RUN_TESTS_ARG: 
2412:  CI_TEST_PKG: fleetctl
2413:  NEED_DOCKER: 1
2414:  ARTIFACT_PREFIX: fleetctl-mysql8.0.44
2415:  GOTOOLCHAIN: local
2416:  ##[endgroup]
2417:  (node:44473) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
2418:  (Use `node --trace-deprecation ...` to show where the warning was created)
2419:  With the provided path, there will be 1 file uploaded
2420:  Artifact name is valid!
2421:  Root directory input is valid!
2422:  Beginning upload of artifact content to blob storage
2423:  (node:44473) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
2424:  Uploaded bytes 2308848
2425:  Finished uploading artifact content to blob storage!
2426:  SHA256 hash of uploaded artifact zip is dbe837a24492adc4eb4b1d328abb719b3ca7f39ad7bd0f2406fb09511ef40151
2427:  Finalizing artifact upload
2428:  Artifact fleetctl-mysql8.0.44-coverage.zip successfully finalized. Artifact ID 8020472174
2429:  Artifact fleetctl-mysql8.0.44-coverage has been successfully uploaded! Final size is 2308848 bytes. Artifact ID is 8020472174
2430:  Artifact download URL: https://github.com/fleetdm/fleet/actions/runs/28541558165/artifacts/8020472174
2431:  ##[group]Run c1grep() { grep "$@" || test $? = 1; }
2432:  �[36;1mc1grep() { grep "$@" || test $? = 1; }�[0m
2433:  �[36;1mc1grep -oP 'FAIL: .*$' /tmp/gotest.log > /tmp/summary.txt�[0m
2434:  �[36;1mc1grep 'test timed out after' /tmp/gotest.log >> /tmp/summary.txt�[0m
2435:  �[36;1mc1grep 'fatal error:' /tmp/gotest.log >> /tmp/summary.txt�[0m
2436:  �[36;1mc1grep -A 10 'panic: runtime error: ' /tmp/gotest.log >> /tmp/summary.txt�[0m
2437:  �[36;1mc1grep ' FAIL\t' /tmp/gotest.log >> /tmp/summary.txt�[0m
2438:  �[36;1mGO_FAIL_SUMMARY=$(head -n 5 /tmp/summary.txt | sed ':a;N;$!ba;s/\n/\\n/g')�[0m
2439:  �[36;1mecho "GO_FAIL_SUMMARY=$GO_FAIL_SUMMARY"�[0m
2440:  �[36;1mif [[ -z "$GO_FAIL_SUMMARY" ]]; then�[0m
2441:  �[36;1m  GO_FAIL_SUMMARY="unknown, please check the build URL"�[0m
2442:  �[36;1mfi�[0m
2443:  �[36;1mGO_FAIL_SUMMARY=$GO_FAIL_SUMMARY envsubst < .github/workflows/config/slack_payload_template.json > ./payload.json�[0m
2444:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
2445:  env:
2446:  RACE_ENABLED: false
2447:  GO_TEST_TIMEOUT: 20m
2448:  DOCKER_COMMAND: docker compose -f docker-compose.yml -f docker-compose-redis-cluster.yml up -d mysql_test mysql_replica_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup s3 saml_idp mailhog mailpit smtp4dev_test
2449:  RUN_TESTS_ARG: 
2450:  CI_TEST_PKG: fleetctl
2451:  NEED_DOCKER: 1
2452:  ARTIFACT_PREFIX: fleetctl-mysql8.0.44
2453:  GOTOOLCHAIN: local
2454:  ##[endgroup]
2455:  GO_FAIL_SUMMARY=FAIL: TestGitOpsFullGlobal/useDeprecatedKeys=false (0.04s)\nFAIL: TestGitOpsFullGlobal/useDeprecatedKeys=true (0.04s)
2456:  Node 20 is being deprecated. This workflow is running with Node 24 by default. If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
2457:  ##[group]Run actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a
2458:  with:
2459:  name: fleetctl-mysql8.0.44-test-log
2460:  path: /tmp/gotest.log
2461:  if-no-files-found: error
2462:  compression-level: 6
...

2465:  RACE_ENABLED: false
2466:  GO_TEST_TIMEOUT: 20m
2467:  DOCKER_COMMAND: docker compose -f docker-compose.yml -f docker-compose-redis-cluster.yml up -d mysql_test mysql_replica_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup s3 saml_idp mailhog mailpit smtp4dev_test
2468:  RUN_TESTS_ARG: 
2469:  CI_TEST_PKG: fleetctl
2470:  NEED_DOCKER: 1
2471:  ARTIFACT_PREFIX: fleetctl-mysql8.0.44
2472:  GOTOOLCHAIN: local
2473:  ##[endgroup]
2474:  (node:44497) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
2475:  (Use `node --trace-deprecation ...` to show where the warning was created)
2476:  With the provided path, there will be 1 file uploaded
2477:  Artifact name is valid!
2478:  Root directory input is valid!
2479:  Beginning upload of artifact content to blob storage
2480:  (node:44497) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
2481:  Uploaded bytes 10968
...

2497:  RACE_ENABLED: false
2498:  GO_TEST_TIMEOUT: 20m
2499:  DOCKER_COMMAND: docker compose -f docker-compose.yml -f docker-compose-redis-cluster.yml up -d mysql_test mysql_replica_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup s3 saml_idp mailhog mailpit smtp4dev_test
2500:  RUN_TESTS_ARG: 
2501:  CI_TEST_PKG: fleetctl
2502:  NEED_DOCKER: 1
2503:  ARTIFACT_PREFIX: fleetctl-mysql8.0.44
2504:  GOTOOLCHAIN: local
2505:  ##[endgroup]
2506:  (node:44509) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
2507:  (Use `node --trace-deprecation ...` to show where the warning was created)
2508:  With the provided path, there will be 1 file uploaded
2509:  Artifact name is valid!
2510:  Root directory input is valid!
2511:  Beginning upload of artifact content to blob storage
2512:  (node:44509) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
2513:  Uploaded bytes 205
...

2529:  RACE_ENABLED: false
2530:  GO_TEST_TIMEOUT: 20m
2531:  DOCKER_COMMAND: docker compose -f docker-compose.yml -f docker-compose-redis-cluster.yml up -d mysql_test mysql_replica_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup s3 saml_idp mailhog mailpit smtp4dev_test
2532:  RUN_TESTS_ARG: 
2533:  CI_TEST_PKG: fleetctl
2534:  NEED_DOCKER: 1
2535:  ARTIFACT_PREFIX: fleetctl-mysql8.0.44
2536:  GOTOOLCHAIN: local
2537:  ##[endgroup]
2538:  (node:44521) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
2539:  (Use `node --trace-deprecation ...` to show where the warning was created)
2540:  With the provided path, there will be 1 file uploaded
2541:  Artifact name is valid!
2542:  Root directory input is valid!
2543:  Beginning upload of artifact content to blob storage
2544:  (node:44521) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
2545:  Uploaded bytes 104738
...

2578:  RACE_ENABLED: false
2579:  GO_TEST_TIMEOUT: 20m
2580:  DOCKER_COMMAND: docker compose -f docker-compose.yml -f docker-compose-redis-cluster.yml up -d mysql_test mysql_replica_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup s3 saml_idp mailhog mailpit smtp4dev_test
2581:  RUN_TESTS_ARG: 
2582:  CI_TEST_PKG: fleetctl
2583:  NEED_DOCKER: 1
2584:  ARTIFACT_PREFIX: fleetctl-mysql8.0.44
2585:  GOTOOLCHAIN: local
2586:  ##[endgroup]
2587:  (node:44534) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
2588:  (Use `node --trace-deprecation ...` to show where the warning was created)
2589:  With the provided path, there will be 1 file uploaded
2590:  Artifact name is valid!
2591:  Root directory input is valid!
2592:  Beginning upload of artifact content to blob storage
2593:  (node:44534) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
2594:  Uploaded bytes 133

@getvictor getvictor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine to get started. Have you run it on a bunch of devices already?
The main issue is I'm not sure if the certificates flow works. But then again we don't expect it to be the main source of the load.

Comment thread cmd/osquery-perf/android_agent.go Outdated

// getCertificateTemplate fetches a certificate template from Fleet.
func (a *androidAgent) getCertificateTemplate(certID uint) (*certTemplateInfo, error) {
url := fmt.Sprintf("%s/api/v1/fleet/fleetd/certificates/%d", a.serverAddress, certID)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the path is /api/fleetd/certificates/{id} with no version prefix

Comment thread cmd/android-amapi-mock/handlers.go Outdated
var req struct {
Applications []struct {
ManagedConfiguration json.RawMessage `json:"managedConfiguration"`
} `json:"applications"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this right? I believe we use modifyPolicyApplications which sends something like: {"changes":[{"application":{"managedConfiguration":...}}]}

defer statusTicker.Stop()

// Track which certificate templates we've already verified so we don't re-verify
verifiedCerts := make(map[uint]struct{})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renewal flow uses the same template ID. But we can worry about renewals later.

Comment thread cmd/osquery-perf/android_agent.go Outdated
}

// Process certificate templates from the proxy state
for _, certID := range state.PendingCertificates {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where/how PendingCertificates are cleared.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PendingCertificates on the fake device are never cleared. The agent checks Fleet's status on every poll and skips certs that aren't "delivered" or are already verified. We could clear them after the agent processes them. But that would break renewals because the agent wouldn't know about the cert anymore after clearing.

}

// sendEnrollment sends an ENROLLMENT PubSub message to Fleet.
func (a *androidAgent) sendEnrollment() error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. This seems similar to sendStatusReport. Maybe we can use a helper?

Comment thread cmd/android-amapi-mock/handlers.go Outdated
d.mu.Lock()
resp := map[string]any{
"name": name,
"appliedPolicyVersion": d.PolicyVersion,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are versions numbers or strings?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}

func handleEnterprisesList(store *deviceStore) http.HandlerFunc {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Check to make sure this works as expected with server's cron:

	// VerifyExistingEnterpriseIfAny checks if there's an existing enterprise in the database
	// and if so, verifies it still exists in Google API. If it doesn't exist, performs cleanup.
	// Returns fleet.IsNotFound error if enterprise was deleted, nil if no enterprise exists or verification passed.
	VerifyExistingEnterpriseIfAny(ctx context.Context) error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cleanup cron runs as part of the cleanups_then_aggregation schedule which is every ~25 minutes. osquery-perf's --start_period is 5 minutes. So fake devices will register well before the first cleanup cron fires.

PendingCommands: d.PendingCommands,
PendingCertificates: d.PendingCertificates,
}
d.PendingCommands = nil

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. PendingCommands is cleared. Can we get stuck or miss a command if there is an issue on the agent? Maybe we don't want a BYOD agent to support an enterprise command, for example.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command consumption is fire-and-forget for load testing purposes. Command reliability and BYOD filtering are Fleet server-side concerns. I think this is acceptable for a load test?

Comment thread cmd/osquery-perf/agent.go
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = tlsConfig
http.DefaultClient.Transport = tr
http.DefaultClient.Timeout = 30 * time.Second

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the agent hanging for 30+ seconds during load tests?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was no actual hang observed. It is a defensive timeout. I chose 30 seconds as an upper bound so requests under load would stop rather than blocking the goroutine forever.

Comment thread cmd/osquery-perf/android_agent.go Outdated
}

// Step 2: Send ENROLLMENT PubSub to Fleet
if err := a.sendEnrollment(); err != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we try to enroll 100 agents and some fail to enroll initially are we okay with them not retrying?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. Now each enrollment step retries up to 5 attempts (3 for orbit) with linear backoff (5s, 10s, 15s, 20s, 25s).

Comment thread cmd/osquery-perf/android_agent.go Outdated
Comment thread cmd/osquery-perf/android_agent.go Outdated
@ksykulev
ksykulev requested a review from dantecatalfamo July 10, 2026 20:38

@dantecatalfamo dantecatalfamo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦞 L(obster)
🧄 G(arlic)
🌮 T(aco)
🔍 M(agnify)

@lukeheath
lukeheath merged commit fd4f92a into main Jul 17, 2026
43 checks passed
@lukeheath
lukeheath deleted the 26225-android-load-testing branch July 17, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android: Load testing

5 participants