Files
fleet/changes/44950-gitops-null-label-id
T
Sharon Katz f3f830bd9d Fix GitOps failure when moving labels from global to fleet scope (#44983)
Closes #44950

## Local reproduction

Reproduced locally using a MySQL integration test against the local test
database. The test simulates the exact GitOps scenario from the issue:

1. Create a label and associate it with an MDM profile
2. Delete the label (FK `ON DELETE SET NULL` sets `label_id = NULL`)
3. Create a new label with the **same name** (simulates moving from
global to fleet scope)
4. Call `batchSetProfileLabelAssociationsDB` with the profile
referencing the new label

**Before fix** (code from `main`, unfixed):
```
$ MYSQL_TEST=1 go test -run "TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated" -v -count=1 ./server/datastore/mysql/...

=== RUN   TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_darwin
    Error: selecting existing profile labels: sql: Scan error on column index 1, name "label_id": converting NULL to uint is unsupported
=== RUN   TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_windows
    Error: selecting existing profile labels: sql: Scan error on column index 1, name "label_id": converting NULL to uint is unsupported
--- FAIL: TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_darwin (0.02s)
--- FAIL: TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_windows (0.02s)
FAIL
```

**After fix:**
```
$ MYSQL_TEST=1 go test -run "TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated" -v -count=1 ./server/datastore/mysql/...

=== RUN   TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_windows
=== RUN   TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_darwin
--- PASS: TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_windows (0.03s)
--- PASS: TestMDMShared/TestBatchSetProfileLabelAssociations/same_label_name_recreated_after_deletion_darwin (0.03s)
PASS
ok  github.com/fleetdm/fleet/v4/server/datastore/mysql  2.761s
```

## Code changes

When a label is deleted, MySQL's `ON DELETE SET NULL` foreign key
constraint automatically sets `label_id = NULL` in the profile-label
association row. The Go code then crashes trying to scan that NULL into
a `uint` field.

- **`server/datastore/mysql/mdm.go`** — Added `COALESCE(label_id, 0)` to
the SELECT in `batchSetProfileLabelAssociationsDB`, so that NULL
`label_id` values are returned as 0 instead of causing a scan error when
Go tries to read NULL into a `uint`.
- **`server/datastore/mysql/apple_mdm.go`** — Same `COALESCE(label_id,
0)` fix in `batchSetDeclarationLabelAssociationsDB`. Also added `OR
label_id IS NULL` to the DELETE statement to clean up broken rows,
matching the profile labels behavior from #42637.

Other queries in the same codebase (e.g.,
`listProfileLabelsForProfiles`) already use `COALESCE(label_id, 0)` —
these two were missed.

## Testing

- `same_label_name_recreated_after_deletion_{darwin,windows}` —
reproduces the exact bug: associates a profile with a label, deletes the
label (NULL label_id), creates a new label with the same name, and
verifies `batchSetProfileLabelAssociationsDB` succeeds, the broken row
is cleaned up, and the correct label association exists
- Full MDM test suite passes: `MYSQL_TEST=1 go test -run "TestMDM"
./server/datastore/mysql/...` (76s)
- `make lint-go-incremental` passes
2026-05-08 07:49:23 +02:00

2 lines
248 B
Plaintext

* Fixed a GitOps failure ("converting NULL to uint is unsupported") when moving labels from global to fleet scope, caused by deleted label associations with NULL `label_id` values in `mdm_configuration_profile_labels` and `mdm_declaration_labels`.