[fix-no-Query-field] fleet-mcp: expose policy SQL in get_policies response (#45513)
## Summary
The `get_policies` MCP tool wasn't returning each policy's osquery SQL,
making it impossible to diagnose policy behavior via MCP — you had to
fall back to `curl` against `GET /api/v1/fleet/global/policies/{id}` to
read the `query` field.
Root cause: the `Policy` struct in
`tools/fleet-mcp/fleet_integration.go` didn't declare a `Query` field,
so Go's JSON decoder silently dropped the key from the Fleet API
response. One-line fix on the struct; the same struct backs both the
global path and the per-team fan-out, so both are covered.
Purely additive — no existing field changes shape, name, or type.
# Checklist for submitter
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Fleet policies now include a query field so the actual policy
query/SQL content is available with policy metadata.
* **Tests**
* Added integration-style test coverage to confirm the query field is
parsed and returned (including empty queries) and that policy responses
contain the expected entries.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45513)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: nulmete <nicoulmete1@gmail.com>
This commit is contained in:
@@ -216,6 +216,7 @@ type Policy struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Query string `json:"query"`
|
||||
Platform string `json:"platform"`
|
||||
PassingHostCount int `json:"passing_host_count"`
|
||||
FailingHostCount int `json:"failing_host_count"`
|
||||
|
||||
@@ -1070,3 +1070,36 @@ func TestGetHostSoftware_SourceFilterAndPerPage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPolicies_IncludesQueryField(t *testing.T) {
|
||||
const wantSQL = "SELECT 1;"
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/v1/fleet/global/policies":
|
||||
_, _ = w.Write([]byte(`{"policies":[
|
||||
{"id":1,"name":"with sql","query":"` + wantSQL + `"},
|
||||
{"id":2,"name":"empty sql","query":""}
|
||||
]}`))
|
||||
case "/api/v1/fleet/teams":
|
||||
_, _ = w.Write([]byte(`{"teams":[]}`))
|
||||
default:
|
||||
http.Error(w, "unexpected path "+r.URL.Path, http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
fc := newTestClient(srv.URL)
|
||||
policies, err := fc.GetPolicies(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(policies) != 2 {
|
||||
t.Fatalf("expected 2 policies, got %d", len(policies))
|
||||
}
|
||||
if policies[0].Query != wantSQL {
|
||||
t.Errorf("policy 1 Query = %q, want %q", policies[0].Query, wantSQL)
|
||||
}
|
||||
if policies[1].Query != "" {
|
||||
t.Errorf("policy 2 Query = %q, want empty string", policies[1].Query)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user