goharbor/harbor · error · lib/errors.Error

PRECONDITION

PRECONDITION

Error message

Provider [%s] cannot be deleted as some preheat policies are using it

What it means

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.

Source

Thrown at src/controller/p2p/preheat/controller.go:220

// DeleteInstance implements @Controller.Delete
func (c *controller) DeleteInstance(ctx context.Context, id int64) error {
	ins, err := c.GetInstance(ctx, id)
	if err != nil {
		return err
	}
	// delete instance should check the instance whether be used by policies
	policies, err := c.ListPolicies(ctx, &q.Query{
		Keywords: map[string]any{
			"provider_id": id,
		},
	})
	if err != nil {
		return err
	}

	if len(policies) > 0 {
		return errors.New(nil).
			WithCode(errors.PreconditionCode).
			WithMessagef("Provider [%s] cannot be deleted as some preheat policies are using it", ins.Name)
	}

	return c.iManager.Delete(ctx, id)
}

// UpdateInstance implements @Controller.Update
func (c *controller) UpdateInstance(ctx context.Context, instance *providerModels.Instance, properties ...string) error {
	oldIns, err := c.GetInstance(ctx, instance.ID)
	if err != nil {
		return err
	}

	if !instance.Enabled {
		// update instance should check the instance whether be used by policies
		policies, err := c.ListPolicies(ctx, &q.Query{
			Keywords: map[string]any{

View on GitHub (pinned to 7b2fd08cc5)

Solutions

  1. List dependent policies first (GET /api/v2/preheat/policies?project_id=... filtered by provider) and delete or point them to another instance.
  2. Only after the policy count is zero, retry the instance DELETE.
  3. If policies are obsolete, bulk-delete them before decommissioning the provider.

Example fix

# before
DELETE /api/v2/preheat/instances/dragonfly

# after
GET  /api/v2/preheat/policies?project_id=1   # find policies with provider_id of the instance
DELETE /api/v2/preheat/policies/{policy_id}   # repeat for each
DELETE /api/v2/preheat/instances/dragonfly
Defensive patterns

Strategy: validation

Validate before calling

// count dependents before deleting an instance:
policies, err := preheatCtl.ListPolicies(ctx, q.New(q.KeyWords{"provider_id": instanceID}))
if err != nil { return err }
if len(policies) > 0 {
    return fmt.Errorf("delete %d dependent policies first", len(policies))
}
return preheatCtl.DeleteInstance(ctx, instanceID)

Try / catch

if err := preheatCtl.DeleteInstance(ctx, id); err != nil {
    if liberrors.IsErr(err, liberrors.PreconditionCode) {
        // fetch policies with provider_id=id, remove/repoint them, retry
    }
    return err
}

Prevention

When it happens

Trigger: DELETE /api/v2/preheat/instances/{name} while one or more preheat policies use this instance as their provider.

Common situations: Decommissioning a Dragonfly/kraken instance without first removing its policies; shared instance retired while teams still have policies; cleanup scripts ignoring dependencies.

Related errors


AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16). Data as JSON: /api/errors/7b67ed98f214b6a2. Report an issue: GitHub.