Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions cmd/api/handlers/environments_crud.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,26 @@ func (h *HandlersApi) EnvironmentConfigPatchHandler(w http.ResponseWriter, r *ht
return
}
}
// Recompose the assembled `configuration` blob from the (possibly just-
// updated) parts. The Update* calls above only write their own column;
// without this recompose the env's `configuration` field — which is what
// GET .../configuration/assembled returns and what agents receive on
// their next /config refresh — stays at the pre-patch value, so edits made
// here never show up on the enroll page's Configuration tab. Flags is not
// part of the composed osquery config, so a flags-only patch skips it.
composedChanged := false
for _, k := range []string{"options", "schedule", "packs", "decorators", "atc"} {
if _, ok := normalized[k]; ok {
composedChanged = true
break
}
}
if composedChanged {
if err := h.Envs.RefreshConfiguration(envVar); err != nil {
apiErrorResponse(w, "error refreshing configuration", http.StatusInternalServerError, err)
return
}
}
h.AuditLog.ConfAction(ctx[ctxUser], "config patch on env "+env.Name, strings.Split(r.RemoteAddr, ":")[0], env.ID)
updated, _ := h.Envs.Get(envVar)
resp := types.EnvConfigResponse{
Expand Down
46 changes: 46 additions & 0 deletions pkg/environments/config_refresh_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package environments

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestRefreshConfigurationRecomposesAfterPartUpdate locks the contract the
// environment-config PATCH handler relies on: UpdateSchedule (and the other
// Update* part writers) only persist their own column — they do NOT recompose
// the assembled `configuration` blob that GET .../configuration/assembled
// returns and that agents receive on /config refresh. RefreshConfiguration
// is what folds the parts back together. Without the handler calling it, a
// schedule edit saved from the SPA's Configuration → Schedule tab never shows
// up on the enroll page's Configuration tab.
func TestRefreshConfigurationRecomposesAfterPartUpdate(t *testing.T) {
db := setupTestDB(t)
envs := CreateEnvironment(db)

env := envs.Empty("dev", "dev.example.com")
require.NoError(t, envs.Create(&env))

schedule := `{"uptime":{"query":"SELECT * FROM uptime;","interval":60}}`
require.NoError(t, envs.UpdateSchedule(env.Name, schedule))

// The bug: the part is saved, but the composed blob is still the empty
// placeholder until RefreshConfiguration runs.
before, err := envs.Get(env.Name)
require.NoError(t, err)
assert.Equal(t, "{}", before.Configuration, "UpdateSchedule must not recompose configuration on its own")

// The fix: RefreshConfiguration folds the updated schedule (and the other
// parts) into the composed blob.
require.NoError(t, envs.RefreshConfiguration(env.Name))

after, err := envs.Get(env.Name)
require.NoError(t, err)
assert.NotEqual(t, "{}", after.Configuration, "RefreshConfiguration should recompose the blob")
assert.True(t, strings.Contains(after.Configuration, "uptime"),
"assembled configuration should contain the scheduled query, got %s", after.Configuration)
assert.True(t, strings.Contains(after.Configuration, "SELECT * FROM uptime;"),
"assembled configuration should contain the query SQL, got %s", after.Configuration)
}
Loading