goharbor/harbor · error · Exception

purge upload age should set with with nh, n is the number of

Error message

purge upload age should set with with nh, n is the number of hour and n should not be less than 2

What it means

The SBOM report DAO's DeleteMany refuses to run when the query carries no keywords. With an empty query, orm.QuerySetter would match every row of the sbom report table, so this error is an explicit guard against an accidental full-table wipe. It is intentional data protection, not a bug.

Source

Thrown at make/photon/prepare/models.py:218

    def __init__(self, config: dict):
        if not config:
            self.enabled = False
        self.enabled = config.get('enabled')
        self.age = config.get('age') or '168h'
        self.interval = config.get('interval') or '24h'
        self.dryrun = config.get('dryrun') or False
        return

    def validate(self):
        if not self.enabled:
            return
        # age should end with h
        if not isinstance(self.age, str) or not self.age.endswith('h'):
            raise Exception('purge upload age should set with with nh, n is the number of hour')
        # interval should larger than 2h
        age = self.age[:-1]
        if not age.isnumeric() or int(age) < 2:
            raise Exception('purge upload age should set with with nh, n is the number of hour and n should not be less than 2')

        # interval should end with h
        if not isinstance(self.interval, str) or not self.interval.endswith('h'):
            raise Exception('purge upload interval should set with with nh, n is the number of hour')
        # interval should larger than 2h
        interval = self.interval[:-1]
        if not interval.isnumeric() or int(interval) < 2:
            raise Exception('purge upload interval should set with with nh, n is the number of hour and n should not beless than 2')
        return


class Cache:
    def __init__(self, config: dict):
        if not config:
            self.enabled = False
        self.enabled = config.get('enabled')
        self.expire_hours = config.get('expire_hours')

View on GitHub (pinned to 7b2fd08cc5)

Solutions

  1. Always set at least one keyword (digest, registration_uuid, ...) before calling DeleteMany
  2. Assert the filter is non-empty in the layer above the DAO and fail with context
  3. Keep the guard as is — fix the empty input, never bypass the check

Example fix

// before
_, err := sbomDAO.DeleteMany(ctx, query) // query.Keywords may be empty

// after
if len(query.Keywords) == 0 {
    return errors.New("refusing to delete sbom reports: no filter set")
}
_, err := sbomDAO.DeleteMany(ctx, query)
Defensive patterns

Strategy: validation

Validate before calling

if len(query.Keywords) == 0 {
    return fmt.Errorf("refusing bulk delete: no keywords in query")
}
return d.DeleteMany(ctx, query)

Try / catch

if _, err := d.DeleteMany(ctx, query); err != nil {
    if strings.Contains(err.Error(), "delete all sbom reports at once is not allowed") {
        // guard fired: your filter construction is broken — never bypass it
        return fmt.Errorf("cleanup filter empty; aborting delete")
    }
    return err
}

Prevention

When it happens

Trigger: dao.DeleteMany(ctx, q.Query{}) or a q.Query whose Keywords map is nil/empty — e.g. a cleanup job built its digest filter conditionally and the condition never fired.

Common situations: GC or retention jobs constructing keyword filters from possibly-empty inputs; nil-vs-empty-map bugs after refactors; code ported from the vulnerability report DAO assuming different query semantics.

Related errors


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