push_v2_test.go 23 KB

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