When using the copy artifact API to copy an artifact by a specific tag (e.g. :1.0.3), if the source artifact's digest is also tagged with another tag that was pushed more recently (e.g. :1.0.11), the triggered replication job replicates the wrong tag (1.0.11) instead of the requested one (1.0.3).
Steps to reproduce
- Push an image to a source project tagged as both
1.0.3 and 1.0.11 on the same digest, with 1.0.11 pushed after 1.0.3
- Configure a replication rule on the destination project
- Call the copy artifact API:
POST /api/v2.0/projects/<dst-project>/repositories/<repo>/artifacts?from=<src-project>/<repo>:1.0.3
- Observe the triggered replication job, it copies
:1.0.11 instead of :1.0.3
Expected behavior
The replication job copies tag 1.0.3 to the upstream registry.
Actual behavior
The replication job copies tag 1.0.11 (the most recently pushed tag on the same digest).
Root cause
In src/controller/artifact/controller.go, copyDeeply fetches the source artifact with all its tags via GetByReference(..., {WithTag: true}). Tags are sorted by PushTime DESC, so the most recently pushed tag appears first. The full tag list is passed to Ensure, which fires a single push event using option.Tags[0], the most recently pushed tag, ignoring which tag was specified in the copy request.
Fix
In copyDeeply, when the reference is a tag name, only include that specific tag in the list passed to Ensure so the push event fires with the correct tag.
Harbor version
v2.15.0
When using the copy artifact API to copy an artifact by a specific tag (e.g.
:1.0.3), if the source artifact's digest is also tagged with another tag that was pushed more recently (e.g.:1.0.11), the triggered replication job replicates the wrong tag (1.0.11) instead of the requested one (1.0.3).Steps to reproduce
1.0.3and1.0.11on the same digest, with1.0.11pushed after1.0.3:1.0.11instead of:1.0.3Expected behavior
The replication job copies tag
1.0.3to the upstream registry.Actual behavior
The replication job copies tag
1.0.11(the most recently pushed tag on the same digest).Root cause
In
src/controller/artifact/controller.go,copyDeeplyfetches the source artifact with all its tags viaGetByReference(..., {WithTag: true}). Tags are sorted byPushTime DESC, so the most recently pushed tag appears first. The full tag list is passed toEnsure, which fires a single push event usingoption.Tags[0], the most recently pushed tag, ignoring which tag was specified in the copy request.Fix
In
copyDeeply, when the reference is a tag name, only include that specific tag in the list passed toEnsureso the push event fires with the correct tag.Harbor version
v2.15.0