push_v2_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. package distribution
  2. import (
  3. "net/http"
  4. "reflect"
  5. "testing"
  6. "github.com/docker/distribution"
  7. "github.com/docker/distribution/context"
  8. "github.com/docker/distribution/manifest/schema2"
  9. "github.com/docker/distribution/reference"
  10. "github.com/docker/docker/distribution/metadata"
  11. "github.com/docker/docker/layer"
  12. "github.com/docker/docker/pkg/progress"
  13. "github.com/opencontainers/go-digest"
  14. )
  15. func TestGetRepositoryMountCandidates(t *testing.T) {
  16. for _, tc := range []struct {
  17. name string
  18. hmacKey string
  19. targetRepo string
  20. maxCandidates int
  21. metadata []metadata.V2Metadata
  22. candidates []metadata.V2Metadata
  23. }{
  24. {
  25. name: "empty metadata",
  26. targetRepo: "busybox",
  27. maxCandidates: -1,
  28. metadata: []metadata.V2Metadata{},
  29. candidates: []metadata.V2Metadata{},
  30. },
  31. {
  32. name: "one item not matching",
  33. targetRepo: "busybox",
  34. maxCandidates: -1,
  35. metadata: []metadata.V2Metadata{taggedMetadata("key", "dgst", "127.0.0.1/repo")},
  36. candidates: []metadata.V2Metadata{},
  37. },
  38. {
  39. name: "one item matching",
  40. targetRepo: "busybox",
  41. maxCandidates: -1,
  42. metadata: []metadata.V2Metadata{taggedMetadata("hash", "1", "docker.io/library/hello-world")},
  43. candidates: []metadata.V2Metadata{taggedMetadata("hash", "1", "docker.io/library/hello-world")},
  44. },
  45. {
  46. name: "allow missing SourceRepository",
  47. targetRepo: "busybox",
  48. maxCandidates: -1,
  49. metadata: []metadata.V2Metadata{
  50. {Digest: digest.Digest("1")},
  51. {Digest: digest.Digest("3")},
  52. {Digest: digest.Digest("2")},
  53. },
  54. candidates: []metadata.V2Metadata{},
  55. },
  56. {
  57. name: "handle docker.io",
  58. targetRepo: "user/app",
  59. maxCandidates: -1,
  60. metadata: []metadata.V2Metadata{
  61. {Digest: digest.Digest("1"), SourceRepository: "docker.io/user/foo"},
  62. {Digest: digest.Digest("3"), SourceRepository: "docker.io/user/bar"},
  63. {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/app"},
  64. },
  65. candidates: []metadata.V2Metadata{
  66. {Digest: digest.Digest("3"), SourceRepository: "docker.io/user/bar"},
  67. {Digest: digest.Digest("1"), SourceRepository: "docker.io/user/foo"},
  68. {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/app"},
  69. },
  70. },
  71. {
  72. name: "sort more items",
  73. hmacKey: "abcd",
  74. targetRepo: "127.0.0.1/foo/bar",
  75. maxCandidates: -1,
  76. metadata: []metadata.V2Metadata{
  77. taggedMetadata("hash", "1", "docker.io/library/hello-world"),
  78. taggedMetadata("efgh", "2", "127.0.0.1/hello-world"),
  79. taggedMetadata("abcd", "3", "docker.io/library/busybox"),
  80. taggedMetadata("hash", "4", "docker.io/library/busybox"),
  81. taggedMetadata("hash", "5", "127.0.0.1/foo"),
  82. taggedMetadata("hash", "6", "127.0.0.1/bar"),
  83. taggedMetadata("efgh", "7", "127.0.0.1/foo/bar"),
  84. taggedMetadata("abcd", "8", "127.0.0.1/xyz"),
  85. taggedMetadata("hash", "9", "127.0.0.1/foo/app"),
  86. },
  87. candidates: []metadata.V2Metadata{
  88. // first by matching hash
  89. taggedMetadata("abcd", "8", "127.0.0.1/xyz"),
  90. // then by longest matching prefix
  91. taggedMetadata("hash", "9", "127.0.0.1/foo/app"),
  92. taggedMetadata("hash", "5", "127.0.0.1/foo"),
  93. // sort the rest of the matching items in reversed order
  94. taggedMetadata("hash", "6", "127.0.0.1/bar"),
  95. taggedMetadata("efgh", "2", "127.0.0.1/hello-world"),
  96. },
  97. },
  98. {
  99. name: "limit max candidates",
  100. hmacKey: "abcd",
  101. targetRepo: "user/app",
  102. maxCandidates: 3,
  103. metadata: []metadata.V2Metadata{
  104. taggedMetadata("abcd", "1", "docker.io/user/app1"),
  105. taggedMetadata("abcd", "2", "docker.io/user/app/base"),
  106. taggedMetadata("hash", "3", "docker.io/user/app"),
  107. taggedMetadata("abcd", "4", "127.0.0.1/user/app"),
  108. taggedMetadata("hash", "5", "docker.io/user/foo"),
  109. taggedMetadata("hash", "6", "docker.io/app/bar"),
  110. },
  111. candidates: []metadata.V2Metadata{
  112. // first by matching hash
  113. taggedMetadata("abcd", "2", "docker.io/user/app/base"),
  114. taggedMetadata("abcd", "1", "docker.io/user/app1"),
  115. // then by longest matching prefix
  116. // "docker.io/usr/app" is excluded since candidates must
  117. // be from a different repository
  118. taggedMetadata("hash", "5", "docker.io/user/foo"),
  119. },
  120. },
  121. } {
  122. repoInfo, err := reference.ParseNormalizedNamed(tc.targetRepo)
  123. if err != nil {
  124. t.Fatalf("[%s] failed to parse reference name: %v", tc.name, err)
  125. }
  126. candidates := getRepositoryMountCandidates(repoInfo, []byte(tc.hmacKey), tc.maxCandidates, tc.metadata)
  127. if len(candidates) != len(tc.candidates) {
  128. t.Errorf("[%s] got unexpected number of candidates: %d != %d", tc.name, len(candidates), len(tc.candidates))
  129. }
  130. for i := 0; i < len(candidates) && i < len(tc.candidates); i++ {
  131. if !reflect.DeepEqual(candidates[i], tc.candidates[i]) {
  132. t.Errorf("[%s] candidate %d does not match expected: %#+v != %#+v", tc.name, i, candidates[i], tc.candidates[i])
  133. }
  134. }
  135. for i := len(candidates); i < len(tc.candidates); i++ {
  136. t.Errorf("[%s] missing expected candidate at position %d (%#+v)", tc.name, i, tc.candidates[i])
  137. }
  138. for i := len(tc.candidates); i < len(candidates); i++ {
  139. t.Errorf("[%s] got unexpected candidate at position %d (%#+v)", tc.name, i, candidates[i])
  140. }
  141. }
  142. }
  143. func TestLayerAlreadyExists(t *testing.T) {
  144. for _, tc := range []struct {
  145. name string
  146. metadata []metadata.V2Metadata
  147. targetRepo string
  148. hmacKey string
  149. maxExistenceChecks int
  150. checkOtherRepositories bool
  151. remoteBlobs map[digest.Digest]distribution.Descriptor
  152. remoteErrors map[digest.Digest]error
  153. expectedDescriptor distribution.Descriptor
  154. expectedExists bool
  155. expectedError error
  156. expectedRequests []string
  157. expectedAdditions []metadata.V2Metadata
  158. expectedRemovals []metadata.V2Metadata
  159. }{
  160. {
  161. name: "empty metadata",
  162. targetRepo: "busybox",
  163. maxExistenceChecks: 3,
  164. checkOtherRepositories: true,
  165. },
  166. {
  167. name: "single not existent metadata",
  168. targetRepo: "busybox",
  169. metadata: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}},
  170. maxExistenceChecks: 3,
  171. expectedRequests: []string{"pear"},
  172. expectedRemovals: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}},
  173. },
  174. {
  175. name: "access denied",
  176. targetRepo: "busybox",
  177. maxExistenceChecks: 1,
  178. metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}},
  179. remoteErrors: map[digest.Digest]error{digest.Digest("apple"): distribution.ErrAccessDenied},
  180. expectedError: nil,
  181. expectedRequests: []string{"apple"},
  182. },
  183. {
  184. name: "not matching reposies",
  185. targetRepo: "busybox",
  186. maxExistenceChecks: 3,
  187. metadata: []metadata.V2Metadata{
  188. {Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/hello-world"},
  189. {Digest: digest.Digest("orange"), SourceRepository: "docker.io/library/busybox/subapp"},
  190. {Digest: digest.Digest("pear"), SourceRepository: "docker.io/busybox"},
  191. {Digest: digest.Digest("plum"), SourceRepository: "busybox"},
  192. {Digest: digest.Digest("banana"), SourceRepository: "127.0.0.1/busybox"},
  193. },
  194. },
  195. {
  196. name: "check other repositories",
  197. targetRepo: "busybox",
  198. maxExistenceChecks: 10,
  199. checkOtherRepositories: true,
  200. metadata: []metadata.V2Metadata{
  201. {Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/hello-world"},
  202. {Digest: digest.Digest("orange"), SourceRepository: "docker.io/busybox/subapp"},
  203. {Digest: digest.Digest("pear"), SourceRepository: "docker.io/busybox"},
  204. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/library/busybox"},
  205. {Digest: digest.Digest("banana"), SourceRepository: "127.0.0.1/busybox"},
  206. },
  207. expectedRequests: []string{"plum", "apple", "pear", "orange", "banana"},
  208. expectedRemovals: []metadata.V2Metadata{
  209. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/library/busybox"},
  210. },
  211. },
  212. {
  213. name: "find existing blob",
  214. targetRepo: "busybox",
  215. metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}},
  216. maxExistenceChecks: 3,
  217. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple")}},
  218. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer},
  219. expectedExists: true,
  220. expectedRequests: []string{"apple"},
  221. },
  222. {
  223. name: "find existing blob with different hmac",
  224. targetRepo: "busybox",
  225. metadata: []metadata.V2Metadata{{SourceRepository: "docker.io/library/busybox", Digest: digest.Digest("apple"), HMAC: "dummyhmac"}},
  226. maxExistenceChecks: 3,
  227. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple")}},
  228. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer},
  229. expectedExists: true,
  230. expectedRequests: []string{"apple"},
  231. expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}},
  232. },
  233. {
  234. name: "overwrite media types",
  235. targetRepo: "busybox",
  236. metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}},
  237. hmacKey: "key",
  238. maxExistenceChecks: 3,
  239. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple"), MediaType: "custom-media-type"}},
  240. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer},
  241. expectedExists: true,
  242. expectedRequests: []string{"apple"},
  243. expectedAdditions: []metadata.V2Metadata{taggedMetadata("key", "apple", "docker.io/library/busybox")},
  244. },
  245. {
  246. name: "find existing blob among many",
  247. targetRepo: "127.0.0.1/myapp",
  248. hmacKey: "key",
  249. metadata: []metadata.V2Metadata{
  250. taggedMetadata("someotherkey", "pear", "127.0.0.1/myapp"),
  251. taggedMetadata("key", "apple", "127.0.0.1/myapp"),
  252. taggedMetadata("", "plum", "127.0.0.1/myapp"),
  253. },
  254. maxExistenceChecks: 3,
  255. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}},
  256. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("pear"), MediaType: schema2.MediaTypeLayer},
  257. expectedExists: true,
  258. expectedRequests: []string{"apple", "plum", "pear"},
  259. expectedAdditions: []metadata.V2Metadata{taggedMetadata("key", "pear", "127.0.0.1/myapp")},
  260. expectedRemovals: []metadata.V2Metadata{
  261. taggedMetadata("key", "apple", "127.0.0.1/myapp"),
  262. {Digest: digest.Digest("plum"), SourceRepository: "127.0.0.1/myapp"},
  263. },
  264. },
  265. {
  266. name: "reach maximum existence checks",
  267. targetRepo: "user/app",
  268. metadata: []metadata.V2Metadata{
  269. {Digest: digest.Digest("pear"), SourceRepository: "docker.io/user/app"},
  270. {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"},
  271. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"},
  272. {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"},
  273. },
  274. maxExistenceChecks: 3,
  275. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}},
  276. expectedExists: false,
  277. expectedRequests: []string{"banana", "plum", "apple"},
  278. expectedRemovals: []metadata.V2Metadata{
  279. {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"},
  280. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"},
  281. {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"},
  282. },
  283. },
  284. {
  285. name: "zero allowed existence checks",
  286. targetRepo: "user/app",
  287. metadata: []metadata.V2Metadata{
  288. {Digest: digest.Digest("pear"), SourceRepository: "docker.io/user/app"},
  289. {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"},
  290. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"},
  291. {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"},
  292. },
  293. maxExistenceChecks: 0,
  294. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}},
  295. },
  296. {
  297. name: "stat single digest just once",
  298. targetRepo: "busybox",
  299. metadata: []metadata.V2Metadata{
  300. taggedMetadata("key1", "pear", "docker.io/library/busybox"),
  301. taggedMetadata("key2", "apple", "docker.io/library/busybox"),
  302. taggedMetadata("key3", "apple", "docker.io/library/busybox"),
  303. },
  304. maxExistenceChecks: 3,
  305. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}},
  306. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("pear"), MediaType: schema2.MediaTypeLayer},
  307. expectedExists: true,
  308. expectedRequests: []string{"apple", "pear"},
  309. expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}},
  310. expectedRemovals: []metadata.V2Metadata{taggedMetadata("key3", "apple", "docker.io/library/busybox")},
  311. },
  312. {
  313. name: "don't stop on first error",
  314. targetRepo: "user/app",
  315. hmacKey: "key",
  316. metadata: []metadata.V2Metadata{
  317. taggedMetadata("key", "banana", "docker.io/user/app"),
  318. taggedMetadata("key", "orange", "docker.io/user/app"),
  319. taggedMetadata("key", "plum", "docker.io/user/app"),
  320. },
  321. maxExistenceChecks: 3,
  322. remoteErrors: map[digest.Digest]error{"orange": distribution.ErrAccessDenied},
  323. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {}},
  324. expectedError: nil,
  325. expectedRequests: []string{"plum", "orange", "banana"},
  326. expectedRemovals: []metadata.V2Metadata{
  327. taggedMetadata("key", "plum", "docker.io/user/app"),
  328. taggedMetadata("key", "banana", "docker.io/user/app"),
  329. },
  330. },
  331. {
  332. name: "remove outdated metadata",
  333. targetRepo: "docker.io/user/app",
  334. metadata: []metadata.V2Metadata{
  335. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/library/busybox"},
  336. {Digest: digest.Digest("orange"), SourceRepository: "docker.io/user/app"},
  337. },
  338. maxExistenceChecks: 3,
  339. remoteErrors: map[digest.Digest]error{"orange": distribution.ErrBlobUnknown},
  340. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("plum"): {}},
  341. expectedExists: false,
  342. expectedRequests: []string{"orange"},
  343. expectedRemovals: []metadata.V2Metadata{{Digest: digest.Digest("orange"), SourceRepository: "docker.io/user/app"}},
  344. },
  345. {
  346. name: "missing SourceRepository",
  347. targetRepo: "busybox",
  348. metadata: []metadata.V2Metadata{
  349. {Digest: digest.Digest("1")},
  350. {Digest: digest.Digest("3")},
  351. {Digest: digest.Digest("2")},
  352. },
  353. maxExistenceChecks: 3,
  354. expectedExists: false,
  355. expectedRequests: []string{"2", "3", "1"},
  356. },
  357. {
  358. name: "with and without SourceRepository",
  359. targetRepo: "busybox",
  360. metadata: []metadata.V2Metadata{
  361. {Digest: digest.Digest("1")},
  362. {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/busybox"},
  363. {Digest: digest.Digest("3")},
  364. },
  365. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("1"): {Digest: digest.Digest("1")}},
  366. maxExistenceChecks: 3,
  367. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("1"), MediaType: schema2.MediaTypeLayer},
  368. expectedExists: true,
  369. expectedRequests: []string{"2", "3", "1"},
  370. expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("1"), SourceRepository: "docker.io/library/busybox"}},
  371. expectedRemovals: []metadata.V2Metadata{
  372. {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/busybox"},
  373. },
  374. },
  375. } {
  376. repoInfo, err := reference.ParseNormalizedNamed(tc.targetRepo)
  377. if err != nil {
  378. t.Fatalf("[%s] failed to parse reference name: %v", tc.name, err)
  379. }
  380. repo := &mockRepo{
  381. t: t,
  382. errors: tc.remoteErrors,
  383. blobs: tc.remoteBlobs,
  384. requests: []string{},
  385. }
  386. ctx := context.Background()
  387. ms := &mockV2MetadataService{}
  388. pd := &v2PushDescriptor{
  389. hmacKey: []byte(tc.hmacKey),
  390. repoInfo: repoInfo,
  391. layer: &storeLayer{
  392. Layer: layer.EmptyLayer,
  393. },
  394. repo: repo,
  395. v2MetadataService: ms,
  396. pushState: &pushState{remoteLayers: make(map[layer.DiffID]distribution.Descriptor)},
  397. checkedDigests: make(map[digest.Digest]struct{}),
  398. }
  399. desc, exists, err := pd.layerAlreadyExists(ctx, &progressSink{t}, layer.EmptyLayer.DiffID(), tc.checkOtherRepositories, tc.maxExistenceChecks, tc.metadata)
  400. if !reflect.DeepEqual(desc, tc.expectedDescriptor) {
  401. t.Errorf("[%s] got unexpected descriptor: %#+v != %#+v", tc.name, desc, tc.expectedDescriptor)
  402. }
  403. if exists != tc.expectedExists {
  404. t.Errorf("[%s] got unexpected exists: %t != %t", tc.name, exists, tc.expectedExists)
  405. }
  406. if !reflect.DeepEqual(err, tc.expectedError) {
  407. t.Errorf("[%s] got unexpected error: %#+v != %#+v", tc.name, err, tc.expectedError)
  408. }
  409. if len(repo.requests) != len(tc.expectedRequests) {
  410. t.Errorf("[%s] got unexpected number of requests: %d != %d", tc.name, len(repo.requests), len(tc.expectedRequests))
  411. }
  412. for i := 0; i < len(repo.requests) && i < len(tc.expectedRequests); i++ {
  413. if repo.requests[i] != tc.expectedRequests[i] {
  414. t.Errorf("[%s] request %d does not match expected: %q != %q", tc.name, i, repo.requests[i], tc.expectedRequests[i])
  415. }
  416. }
  417. for i := len(repo.requests); i < len(tc.expectedRequests); i++ {
  418. t.Errorf("[%s] missing expected request at position %d (%q)", tc.name, i, tc.expectedRequests[i])
  419. }
  420. for i := len(tc.expectedRequests); i < len(repo.requests); i++ {
  421. t.Errorf("[%s] got unexpected request at position %d (%q)", tc.name, i, repo.requests[i])
  422. }
  423. if len(ms.added) != len(tc.expectedAdditions) {
  424. t.Errorf("[%s] got unexpected number of additions: %d != %d", tc.name, len(ms.added), len(tc.expectedAdditions))
  425. }
  426. for i := 0; i < len(ms.added) && i < len(tc.expectedAdditions); i++ {
  427. if ms.added[i] != tc.expectedAdditions[i] {
  428. t.Errorf("[%s] added metadata at %d does not match expected: %q != %q", tc.name, i, ms.added[i], tc.expectedAdditions[i])
  429. }
  430. }
  431. for i := len(ms.added); i < len(tc.expectedAdditions); i++ {
  432. t.Errorf("[%s] missing expected addition at position %d (%q)", tc.name, i, tc.expectedAdditions[i])
  433. }
  434. for i := len(tc.expectedAdditions); i < len(ms.added); i++ {
  435. t.Errorf("[%s] unexpected metadata addition at position %d (%q)", tc.name, i, ms.added[i])
  436. }
  437. if len(ms.removed) != len(tc.expectedRemovals) {
  438. t.Errorf("[%s] got unexpected number of removals: %d != %d", tc.name, len(ms.removed), len(tc.expectedRemovals))
  439. }
  440. for i := 0; i < len(ms.removed) && i < len(tc.expectedRemovals); i++ {
  441. if ms.removed[i] != tc.expectedRemovals[i] {
  442. t.Errorf("[%s] removed metadata at %d does not match expected: %q != %q", tc.name, i, ms.removed[i], tc.expectedRemovals[i])
  443. }
  444. }
  445. for i := len(ms.removed); i < len(tc.expectedRemovals); i++ {
  446. t.Errorf("[%s] missing expected removal at position %d (%q)", tc.name, i, tc.expectedRemovals[i])
  447. }
  448. for i := len(tc.expectedRemovals); i < len(ms.removed); i++ {
  449. t.Errorf("[%s] removed unexpected metadata at position %d (%q)", tc.name, i, ms.removed[i])
  450. }
  451. }
  452. }
  453. func taggedMetadata(key string, dgst string, sourceRepo string) metadata.V2Metadata {
  454. meta := metadata.V2Metadata{
  455. Digest: digest.Digest(dgst),
  456. SourceRepository: sourceRepo,
  457. }
  458. meta.HMAC = metadata.ComputeV2MetadataHMAC([]byte(key), &meta)
  459. return meta
  460. }
  461. type mockRepo struct {
  462. t *testing.T
  463. errors map[digest.Digest]error
  464. blobs map[digest.Digest]distribution.Descriptor
  465. requests []string
  466. }
  467. var _ distribution.Repository = &mockRepo{}
  468. func (m *mockRepo) Named() reference.Named {
  469. m.t.Fatalf("Named() not implemented")
  470. return nil
  471. }
  472. func (m *mockRepo) Manifests(ctc context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
  473. m.t.Fatalf("Manifests() not implemented")
  474. return nil, nil
  475. }
  476. func (m *mockRepo) Tags(ctc context.Context) distribution.TagService {
  477. m.t.Fatalf("Tags() not implemented")
  478. return nil
  479. }
  480. func (m *mockRepo) Blobs(ctx context.Context) distribution.BlobStore {
  481. return &mockBlobStore{
  482. repo: m,
  483. }
  484. }
  485. type mockBlobStore struct {
  486. repo *mockRepo
  487. }
  488. var _ distribution.BlobStore = &mockBlobStore{}
  489. func (m *mockBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
  490. m.repo.requests = append(m.repo.requests, dgst.String())
  491. if err, exists := m.repo.errors[dgst]; exists {
  492. return distribution.Descriptor{}, err
  493. }
  494. if desc, exists := m.repo.blobs[dgst]; exists {
  495. return desc, nil
  496. }
  497. return distribution.Descriptor{}, distribution.ErrBlobUnknown
  498. }
  499. func (m *mockBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
  500. m.repo.t.Fatal("Get() not implemented")
  501. return nil, nil
  502. }
  503. func (m *mockBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
  504. m.repo.t.Fatal("Open() not implemented")
  505. return nil, nil
  506. }
  507. func (m *mockBlobStore) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) {
  508. m.repo.t.Fatal("Put() not implemented")
  509. return distribution.Descriptor{}, nil
  510. }
  511. func (m *mockBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) {
  512. m.repo.t.Fatal("Create() not implemented")
  513. return nil, nil
  514. }
  515. func (m *mockBlobStore) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) {
  516. m.repo.t.Fatal("Resume() not implemented")
  517. return nil, nil
  518. }
  519. func (m *mockBlobStore) Delete(ctx context.Context, dgst digest.Digest) error {
  520. m.repo.t.Fatal("Delete() not implemented")
  521. return nil
  522. }
  523. func (m *mockBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error {
  524. m.repo.t.Fatalf("ServeBlob() not implemented")
  525. return nil
  526. }
  527. type mockV2MetadataService struct {
  528. added []metadata.V2Metadata
  529. removed []metadata.V2Metadata
  530. }
  531. var _ metadata.V2MetadataService = &mockV2MetadataService{}
  532. func (*mockV2MetadataService) GetMetadata(diffID layer.DiffID) ([]metadata.V2Metadata, error) {
  533. return nil, nil
  534. }
  535. func (*mockV2MetadataService) GetDiffID(dgst digest.Digest) (layer.DiffID, error) {
  536. return "", nil
  537. }
  538. func (m *mockV2MetadataService) Add(diffID layer.DiffID, metadata metadata.V2Metadata) error {
  539. m.added = append(m.added, metadata)
  540. return nil
  541. }
  542. func (m *mockV2MetadataService) TagAndAdd(diffID layer.DiffID, hmacKey []byte, meta metadata.V2Metadata) error {
  543. meta.HMAC = metadata.ComputeV2MetadataHMAC(hmacKey, &meta)
  544. m.Add(diffID, meta)
  545. return nil
  546. }
  547. func (m *mockV2MetadataService) Remove(metadata metadata.V2Metadata) error {
  548. m.removed = append(m.removed, metadata)
  549. return nil
  550. }
  551. type progressSink struct {
  552. t *testing.T
  553. }
  554. func (s *progressSink) WriteProgress(p progress.Progress) error {
  555. s.t.Logf("progress update: %#+v", p)
  556. return nil
  557. }