{"record":{"id":"7b67ed98f214b6a2","repo":"goharbor/harbor","slug":"precondition","errorCode":"PRECONDITION","errorMessage":"Provider [%s] cannot be deleted as some preheat policies are using it","messagePattern":"Provider \\[(.+?)\\] cannot be deleted as some preheat policies are using it","errorType":"http","errorClass":"lib/errors.Error","httpStatus":412,"severity":"error","filePath":"src/controller/p2p/preheat/controller.go","lineNumber":220,"sourceCode":"\n// DeleteInstance implements @Controller.Delete\nfunc (c *controller) DeleteInstance(ctx context.Context, id int64) error {\n\tins, err := c.GetInstance(ctx, id)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// delete instance should check the instance whether be used by policies\n\tpolicies, err := c.ListPolicies(ctx, &q.Query{\n\t\tKeywords: map[string]any{\n\t\t\t\"provider_id\": id,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(policies) > 0 {\n\t\treturn errors.New(nil).\n\t\t\tWithCode(errors.PreconditionCode).\n\t\t\tWithMessagef(\"Provider [%s] cannot be deleted as some preheat policies are using it\", ins.Name)\n\t}\n\n\treturn c.iManager.Delete(ctx, id)\n}\n\n// UpdateInstance implements @Controller.Update\nfunc (c *controller) UpdateInstance(ctx context.Context, instance *providerModels.Instance, properties ...string) error {\n\toldIns, err := c.GetInstance(ctx, instance.ID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !instance.Enabled {\n\t\t// update instance should check the instance whether be used by policies\n\t\tpolicies, err := c.ListPolicies(ctx, &q.Query{\n\t\t\tKeywords: map[string]any{","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/controller/p2p/preheat/controller.go#L202-L238","documentation":"Before deleting a preheat provider instance, the controller lists preheat policies with provider_id = id. If any policy still references the instance, deletion is refused with PRECONDITION (HTTP 412) naming the instance - deleting would orphan those policies.","triggerScenarios":"DELETE /api/v2/preheat/instances/{name} while one or more preheat policies use this instance as their provider.","commonSituations":"Decommissioning a Dragonfly/kraken instance without first removing its policies; shared instance retired while teams still have policies; cleanup scripts ignoring dependencies.","solutions":["List dependent policies first (GET /api/v2/preheat/policies?project_id=... filtered by provider) and delete or point them to another instance.","Only after the policy count is zero, retry the instance DELETE.","If policies are obsolete, bulk-delete them before decommissioning the provider."],"exampleFix":"# before\nDELETE /api/v2/preheat/instances/dragonfly\n\n# after\nGET  /api/v2/preheat/policies?project_id=1   # find policies with provider_id of the instance\nDELETE /api/v2/preheat/policies/{policy_id}   # repeat for each\nDELETE /api/v2/preheat/instances/dragonfly","handlingStrategy":"validation","validationCode":"// count dependents before deleting an instance:\npolicies, err := preheatCtl.ListPolicies(ctx, q.New(q.KeyWords{\"provider_id\": instanceID}))\nif err != nil { return err }\nif len(policies) > 0 {\n    return fmt.Errorf(\"delete %d dependent policies first\", len(policies))\n}\nreturn preheatCtl.DeleteInstance(ctx, instanceID)","typeGuard":null,"tryCatchPattern":"if err := preheatCtl.DeleteInstance(ctx, id); err != nil {\n    if liberrors.IsErr(err, liberrors.PreconditionCode) {\n        // fetch policies with provider_id=id, remove/repoint them, retry\n    }\n    return err\n}","preventionTips":["Decommission preheat instances only after their policies are removed.","Add a dependency check step to cleanup runbooks.","Prefer disabling at the policy level for maintenance windows."],"tags":["p2p-preheat","precondition","delete","referential-integrity"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}