push_v2_test.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. package distribution // import "github.com/docker/docker/distribution"
  2. import (
  3. "context"
  4. "net/http"
  5. "net/url"
  6. "reflect"
  7. "testing"
  8. "github.com/docker/distribution"
  9. "github.com/docker/distribution/manifest/schema2"
  10. "github.com/docker/distribution/reference"
  11. "github.com/docker/distribution/registry/api/errcode"
  12. "github.com/docker/docker/api/types/registry"
  13. "github.com/docker/docker/distribution/metadata"
  14. "github.com/docker/docker/layer"
  15. "github.com/docker/docker/pkg/progress"
  16. refstore "github.com/docker/docker/reference"
  17. registrypkg "github.com/docker/docker/registry"
  18. "github.com/opencontainers/go-digest"
  19. )
  20. func TestGetRepositoryMountCandidates(t *testing.T) {
  21. for _, tc := range []struct {
  22. name string
  23. hmacKey string
  24. targetRepo string
  25. maxCandidates int
  26. metadata []metadata.V2Metadata
  27. candidates []metadata.V2Metadata
  28. }{
  29. {
  30. name: "empty metadata",
  31. targetRepo: "busybox",
  32. maxCandidates: -1,
  33. metadata: []metadata.V2Metadata{},
  34. candidates: []metadata.V2Metadata{},
  35. },
  36. {
  37. name: "one item not matching",
  38. targetRepo: "busybox",
  39. maxCandidates: -1,
  40. metadata: []metadata.V2Metadata{taggedMetadata("key", "dgst", "127.0.0.1/repo")},
  41. candidates: []metadata.V2Metadata{},
  42. },
  43. {
  44. name: "one item matching",
  45. targetRepo: "busybox",
  46. maxCandidates: -1,
  47. metadata: []metadata.V2Metadata{taggedMetadata("hash", "1", "docker.io/library/hello-world")},
  48. candidates: []metadata.V2Metadata{taggedMetadata("hash", "1", "docker.io/library/hello-world")},
  49. },
  50. {
  51. name: "allow missing SourceRepository",
  52. targetRepo: "busybox",
  53. maxCandidates: -1,
  54. metadata: []metadata.V2Metadata{
  55. {Digest: digest.Digest("1")},
  56. {Digest: digest.Digest("3")},
  57. {Digest: digest.Digest("2")},
  58. },
  59. candidates: []metadata.V2Metadata{},
  60. },
  61. {
  62. name: "handle docker.io",
  63. targetRepo: "user/app",
  64. maxCandidates: -1,
  65. metadata: []metadata.V2Metadata{
  66. {Digest: digest.Digest("1"), SourceRepository: "docker.io/user/foo"},
  67. {Digest: digest.Digest("3"), SourceRepository: "docker.io/user/bar"},
  68. {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/app"},
  69. },
  70. candidates: []metadata.V2Metadata{
  71. {Digest: digest.Digest("3"), SourceRepository: "docker.io/user/bar"},
  72. {Digest: digest.Digest("1"), SourceRepository: "docker.io/user/foo"},
  73. {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/app"},
  74. },
  75. },
  76. {
  77. name: "sort more items",
  78. hmacKey: "abcd",
  79. targetRepo: "127.0.0.1/foo/bar",
  80. maxCandidates: -1,
  81. metadata: []metadata.V2Metadata{
  82. taggedMetadata("hash", "1", "docker.io/library/hello-world"),
  83. taggedMetadata("efgh", "2", "127.0.0.1/hello-world"),
  84. taggedMetadata("abcd", "3", "docker.io/library/busybox"),
  85. taggedMetadata("hash", "4", "docker.io/library/busybox"),
  86. taggedMetadata("hash", "5", "127.0.0.1/foo"),
  87. taggedMetadata("hash", "6", "127.0.0.1/bar"),
  88. taggedMetadata("efgh", "7", "127.0.0.1/foo/bar"),
  89. taggedMetadata("abcd", "8", "127.0.0.1/xyz"),
  90. taggedMetadata("hash", "9", "127.0.0.1/foo/app"),
  91. },
  92. candidates: []metadata.V2Metadata{
  93. // first by matching hash
  94. taggedMetadata("abcd", "8", "127.0.0.1/xyz"),
  95. // then by longest matching prefix
  96. taggedMetadata("hash", "9", "127.0.0.1/foo/app"),
  97. taggedMetadata("hash", "5", "127.0.0.1/foo"),
  98. // sort the rest of the matching items in reversed order
  99. taggedMetadata("hash", "6", "127.0.0.1/bar"),
  100. taggedMetadata("efgh", "2", "127.0.0.1/hello-world"),
  101. },
  102. },
  103. {
  104. name: "limit max candidates",
  105. hmacKey: "abcd",
  106. targetRepo: "user/app",
  107. maxCandidates: 3,
  108. metadata: []metadata.V2Metadata{
  109. taggedMetadata("abcd", "1", "docker.io/user/app1"),
  110. taggedMetadata("abcd", "2", "docker.io/user/app/base"),
  111. taggedMetadata("hash", "3", "docker.io/user/app"),
  112. taggedMetadata("abcd", "4", "127.0.0.1/user/app"),
  113. taggedMetadata("hash", "5", "docker.io/user/foo"),
  114. taggedMetadata("hash", "6", "docker.io/app/bar"),
  115. },
  116. candidates: []metadata.V2Metadata{
  117. // first by matching hash
  118. taggedMetadata("abcd", "2", "docker.io/user/app/base"),
  119. taggedMetadata("abcd", "1", "docker.io/user/app1"),
  120. // then by longest matching prefix
  121. // "docker.io/usr/app" is excluded since candidates must
  122. // be from a different repository
  123. taggedMetadata("hash", "5", "docker.io/user/foo"),
  124. },
  125. },
  126. } {
  127. repoInfo, err := reference.ParseNormalizedNamed(tc.targetRepo)
  128. if err != nil {
  129. t.Fatalf("[%s] failed to parse reference name: %v", tc.name, err)
  130. }
  131. candidates := getRepositoryMountCandidates(repoInfo, []byte(tc.hmacKey), tc.maxCandidates, tc.metadata)
  132. if len(candidates) != len(tc.candidates) {
  133. t.Errorf("[%s] got unexpected number of candidates: %d != %d", tc.name, len(candidates), len(tc.candidates))
  134. }
  135. for i := 0; i < len(candidates) && i < len(tc.candidates); i++ {
  136. if !reflect.DeepEqual(candidates[i], tc.candidates[i]) {
  137. t.Errorf("[%s] candidate %d does not match expected: %#+v != %#+v", tc.name, i, candidates[i], tc.candidates[i])
  138. }
  139. }
  140. for i := len(candidates); i < len(tc.candidates); i++ {
  141. t.Errorf("[%s] missing expected candidate at position %d (%#+v)", tc.name, i, tc.candidates[i])
  142. }
  143. for i := len(tc.candidates); i < len(candidates); i++ {
  144. t.Errorf("[%s] got unexpected candidate at position %d (%#+v)", tc.name, i, candidates[i])
  145. }
  146. }
  147. }
  148. func TestLayerAlreadyExists(t *testing.T) {
  149. for _, tc := range []struct {
  150. name string
  151. metadata []metadata.V2Metadata
  152. targetRepo string
  153. hmacKey string
  154. maxExistenceChecks int
  155. checkOtherRepositories bool
  156. remoteBlobs map[digest.Digest]distribution.Descriptor
  157. remoteErrors map[digest.Digest]error
  158. expectedDescriptor distribution.Descriptor
  159. expectedExists bool
  160. expectedError error
  161. expectedRequests []string
  162. expectedAdditions []metadata.V2Metadata
  163. expectedRemovals []metadata.V2Metadata
  164. }{
  165. {
  166. name: "empty metadata",
  167. targetRepo: "busybox",
  168. maxExistenceChecks: 3,
  169. checkOtherRepositories: true,
  170. },
  171. {
  172. name: "single not existent metadata",
  173. targetRepo: "busybox",
  174. metadata: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}},
  175. maxExistenceChecks: 3,
  176. expectedRequests: []string{"pear"},
  177. expectedRemovals: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}},
  178. },
  179. {
  180. name: "access denied",
  181. targetRepo: "busybox",
  182. maxExistenceChecks: 1,
  183. metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}},
  184. remoteErrors: map[digest.Digest]error{digest.Digest("apple"): distribution.ErrAccessDenied},
  185. expectedError: nil,
  186. expectedRequests: []string{"apple"},
  187. },
  188. {
  189. name: "not matching repositories",
  190. targetRepo: "busybox",
  191. maxExistenceChecks: 3,
  192. metadata: []metadata.V2Metadata{
  193. {Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/hello-world"},
  194. {Digest: digest.Digest("orange"), SourceRepository: "docker.io/library/busybox/subapp"},
  195. {Digest: digest.Digest("pear"), SourceRepository: "docker.io/busybox"},
  196. {Digest: digest.Digest("plum"), SourceRepository: "busybox"},
  197. {Digest: digest.Digest("banana"), SourceRepository: "127.0.0.1/busybox"},
  198. },
  199. },
  200. {
  201. name: "check other repositories",
  202. targetRepo: "busybox",
  203. maxExistenceChecks: 10,
  204. checkOtherRepositories: true,
  205. metadata: []metadata.V2Metadata{
  206. {Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/hello-world"},
  207. {Digest: digest.Digest("orange"), SourceRepository: "docker.io/busybox/subapp"},
  208. {Digest: digest.Digest("pear"), SourceRepository: "docker.io/busybox"},
  209. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/library/busybox"},
  210. {Digest: digest.Digest("banana"), SourceRepository: "127.0.0.1/busybox"},
  211. },
  212. expectedRequests: []string{"plum", "apple", "pear", "orange", "banana"},
  213. expectedRemovals: []metadata.V2Metadata{
  214. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/library/busybox"},
  215. },
  216. },
  217. {
  218. name: "find existing blob",
  219. targetRepo: "busybox",
  220. metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}},
  221. maxExistenceChecks: 3,
  222. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple")}},
  223. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer},
  224. expectedExists: true,
  225. expectedRequests: []string{"apple"},
  226. },
  227. {
  228. name: "find existing blob with different hmac",
  229. targetRepo: "busybox",
  230. metadata: []metadata.V2Metadata{{SourceRepository: "docker.io/library/busybox", Digest: digest.Digest("apple"), HMAC: "dummyhmac"}},
  231. maxExistenceChecks: 3,
  232. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple")}},
  233. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer},
  234. expectedExists: true,
  235. expectedRequests: []string{"apple"},
  236. expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}},
  237. },
  238. {
  239. name: "overwrite media types",
  240. targetRepo: "busybox",
  241. metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}},
  242. hmacKey: "key",
  243. maxExistenceChecks: 3,
  244. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple"), MediaType: "custom-media-type"}},
  245. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer},
  246. expectedExists: true,
  247. expectedRequests: []string{"apple"},
  248. expectedAdditions: []metadata.V2Metadata{taggedMetadata("key", "apple", "docker.io/library/busybox")},
  249. },
  250. {
  251. name: "find existing blob among many",
  252. targetRepo: "127.0.0.1/myapp",
  253. hmacKey: "key",
  254. metadata: []metadata.V2Metadata{
  255. taggedMetadata("someotherkey", "pear", "127.0.0.1/myapp"),
  256. taggedMetadata("key", "apple", "127.0.0.1/myapp"),
  257. taggedMetadata("", "plum", "127.0.0.1/myapp"),
  258. },
  259. maxExistenceChecks: 3,
  260. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}},
  261. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("pear"), MediaType: schema2.MediaTypeLayer},
  262. expectedExists: true,
  263. expectedRequests: []string{"apple", "plum", "pear"},
  264. expectedAdditions: []metadata.V2Metadata{taggedMetadata("key", "pear", "127.0.0.1/myapp")},
  265. expectedRemovals: []metadata.V2Metadata{
  266. taggedMetadata("key", "apple", "127.0.0.1/myapp"),
  267. {Digest: digest.Digest("plum"), SourceRepository: "127.0.0.1/myapp"},
  268. },
  269. },
  270. {
  271. name: "reach maximum existence checks",
  272. targetRepo: "user/app",
  273. metadata: []metadata.V2Metadata{
  274. {Digest: digest.Digest("pear"), SourceRepository: "docker.io/user/app"},
  275. {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"},
  276. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"},
  277. {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"},
  278. },
  279. maxExistenceChecks: 3,
  280. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}},
  281. expectedExists: false,
  282. expectedRequests: []string{"banana", "plum", "apple"},
  283. expectedRemovals: []metadata.V2Metadata{
  284. {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"},
  285. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"},
  286. {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"},
  287. },
  288. },
  289. {
  290. name: "zero allowed existence checks",
  291. targetRepo: "user/app",
  292. metadata: []metadata.V2Metadata{
  293. {Digest: digest.Digest("pear"), SourceRepository: "docker.io/user/app"},
  294. {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"},
  295. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"},
  296. {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"},
  297. },
  298. maxExistenceChecks: 0,
  299. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}},
  300. },
  301. {
  302. name: "stat single digest just once",
  303. targetRepo: "busybox",
  304. metadata: []metadata.V2Metadata{
  305. taggedMetadata("key1", "pear", "docker.io/library/busybox"),
  306. taggedMetadata("key2", "apple", "docker.io/library/busybox"),
  307. taggedMetadata("key3", "apple", "docker.io/library/busybox"),
  308. },
  309. maxExistenceChecks: 3,
  310. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}},
  311. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("pear"), MediaType: schema2.MediaTypeLayer},
  312. expectedExists: true,
  313. expectedRequests: []string{"apple", "pear"},
  314. expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}},
  315. expectedRemovals: []metadata.V2Metadata{taggedMetadata("key3", "apple", "docker.io/library/busybox")},
  316. },
  317. {
  318. name: "don't stop on first error",
  319. targetRepo: "user/app",
  320. hmacKey: "key",
  321. metadata: []metadata.V2Metadata{
  322. taggedMetadata("key", "banana", "docker.io/user/app"),
  323. taggedMetadata("key", "orange", "docker.io/user/app"),
  324. taggedMetadata("key", "plum", "docker.io/user/app"),
  325. },
  326. maxExistenceChecks: 3,
  327. remoteErrors: map[digest.Digest]error{"orange": distribution.ErrAccessDenied},
  328. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {}},
  329. expectedError: nil,
  330. expectedRequests: []string{"plum", "orange", "banana"},
  331. expectedRemovals: []metadata.V2Metadata{
  332. taggedMetadata("key", "plum", "docker.io/user/app"),
  333. taggedMetadata("key", "banana", "docker.io/user/app"),
  334. },
  335. },
  336. {
  337. name: "remove outdated metadata",
  338. targetRepo: "docker.io/user/app",
  339. metadata: []metadata.V2Metadata{
  340. {Digest: digest.Digest("plum"), SourceRepository: "docker.io/library/busybox"},
  341. {Digest: digest.Digest("orange"), SourceRepository: "docker.io/user/app"},
  342. },
  343. maxExistenceChecks: 3,
  344. remoteErrors: map[digest.Digest]error{"orange": distribution.ErrBlobUnknown},
  345. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("plum"): {}},
  346. expectedExists: false,
  347. expectedRequests: []string{"orange"},
  348. expectedRemovals: []metadata.V2Metadata{{Digest: digest.Digest("orange"), SourceRepository: "docker.io/user/app"}},
  349. },
  350. {
  351. name: "missing SourceRepository",
  352. targetRepo: "busybox",
  353. metadata: []metadata.V2Metadata{
  354. {Digest: digest.Digest("1")},
  355. {Digest: digest.Digest("3")},
  356. {Digest: digest.Digest("2")},
  357. },
  358. maxExistenceChecks: 3,
  359. expectedExists: false,
  360. expectedRequests: []string{"2", "3", "1"},
  361. },
  362. {
  363. name: "with and without SourceRepository",
  364. targetRepo: "busybox",
  365. metadata: []metadata.V2Metadata{
  366. {Digest: digest.Digest("1")},
  367. {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/busybox"},
  368. {Digest: digest.Digest("3")},
  369. },
  370. remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("1"): {Digest: digest.Digest("1")}},
  371. maxExistenceChecks: 3,
  372. expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("1"), MediaType: schema2.MediaTypeLayer},
  373. expectedExists: true,
  374. expectedRequests: []string{"2", "3", "1"},
  375. expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("1"), SourceRepository: "docker.io/library/busybox"}},
  376. expectedRemovals: []metadata.V2Metadata{
  377. {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/busybox"},
  378. },
  379. },
  380. } {
  381. repoInfo, err := reference.ParseNormalizedNamed(tc.targetRepo)
  382. if err != nil {
  383. t.Fatalf("[%s] failed to parse reference name: %v", tc.name, err)
  384. }
  385. repo := &mockRepo{
  386. t: t,
  387. errors: tc.remoteErrors,
  388. blobs: tc.remoteBlobs,
  389. requests: []string{},
  390. }
  391. ctx := context.Background()
  392. ms := &mockV2MetadataService{}
  393. pd := &pushDescriptor{
  394. hmacKey: []byte(tc.hmacKey),
  395. repoInfo: repoInfo,
  396. layer: &storeLayer{
  397. Layer: layer.EmptyLayer,
  398. },
  399. repo: repo,
  400. metadataService: ms,
  401. pushState: &pushState{remoteLayers: make(map[layer.DiffID]distribution.Descriptor)},
  402. checkedDigests: make(map[digest.Digest]struct{}),
  403. }
  404. desc, exists, err := pd.layerAlreadyExists(ctx, &progressSink{t}, layer.EmptyLayer.DiffID(), tc.checkOtherRepositories, tc.maxExistenceChecks, tc.metadata)
  405. if !reflect.DeepEqual(desc, tc.expectedDescriptor) {
  406. t.Errorf("[%s] got unexpected descriptor: %#+v != %#+v", tc.name, desc, tc.expectedDescriptor)
  407. }
  408. if exists != tc.expectedExists {
  409. t.Errorf("[%s] got unexpected exists: %t != %t", tc.name, exists, tc.expectedExists)
  410. }
  411. if !reflect.DeepEqual(err, tc.expectedError) {
  412. t.Errorf("[%s] got unexpected error: %#+v != %#+v", tc.name, err, tc.expectedError)
  413. }
  414. if len(repo.requests) != len(tc.expectedRequests) {
  415. t.Errorf("[%s] got unexpected number of requests: %d != %d", tc.name, len(repo.requests), len(tc.expectedRequests))
  416. }
  417. for i := 0; i < len(repo.requests) && i < len(tc.expectedRequests); i++ {
  418. if repo.requests[i] != tc.expectedRequests[i] {
  419. t.Errorf("[%s] request %d does not match expected: %q != %q", tc.name, i, repo.requests[i], tc.expectedRequests[i])
  420. }
  421. }
  422. for i := len(repo.requests); i < len(tc.expectedRequests); i++ {
  423. t.Errorf("[%s] missing expected request at position %d (%q)", tc.name, i, tc.expectedRequests[i])
  424. }
  425. for i := len(tc.expectedRequests); i < len(repo.requests); i++ {
  426. t.Errorf("[%s] got unexpected request at position %d (%q)", tc.name, i, repo.requests[i])
  427. }
  428. if len(ms.added) != len(tc.expectedAdditions) {
  429. t.Errorf("[%s] got unexpected number of additions: %d != %d", tc.name, len(ms.added), len(tc.expectedAdditions))
  430. }
  431. for i := 0; i < len(ms.added) && i < len(tc.expectedAdditions); i++ {
  432. if ms.added[i] != tc.expectedAdditions[i] {
  433. t.Errorf("[%s] added metadata at %d does not match expected: %q != %q", tc.name, i, ms.added[i], tc.expectedAdditions[i])
  434. }
  435. }
  436. for i := len(ms.added); i < len(tc.expectedAdditions); i++ {
  437. t.Errorf("[%s] missing expected addition at position %d (%q)", tc.name, i, tc.expectedAdditions[i])
  438. }
  439. for i := len(tc.expectedAdditions); i < len(ms.added); i++ {
  440. t.Errorf("[%s] unexpected metadata addition at position %d (%q)", tc.name, i, ms.added[i])
  441. }
  442. if len(ms.removed) != len(tc.expectedRemovals) {
  443. t.Errorf("[%s] got unexpected number of removals: %d != %d", tc.name, len(ms.removed), len(tc.expectedRemovals))
  444. }
  445. for i := 0; i < len(ms.removed) && i < len(tc.expectedRemovals); i++ {
  446. if ms.removed[i] != tc.expectedRemovals[i] {
  447. t.Errorf("[%s] removed metadata at %d does not match expected: %q != %q", tc.name, i, ms.removed[i], tc.expectedRemovals[i])
  448. }
  449. }
  450. for i := len(ms.removed); i < len(tc.expectedRemovals); i++ {
  451. t.Errorf("[%s] missing expected removal at position %d (%q)", tc.name, i, tc.expectedRemovals[i])
  452. }
  453. for i := len(tc.expectedRemovals); i < len(ms.removed); i++ {
  454. t.Errorf("[%s] removed unexpected metadata at position %d (%q)", tc.name, i, ms.removed[i])
  455. }
  456. }
  457. }
  458. type mockReferenceStore struct{}
  459. func (s *mockReferenceStore) References(id digest.Digest) []reference.Named {
  460. return []reference.Named{}
  461. }
  462. func (s *mockReferenceStore) ReferencesByName(ref reference.Named) []refstore.Association {
  463. return []refstore.Association{}
  464. }
  465. func (s *mockReferenceStore) AddTag(ref reference.Named, id digest.Digest, force bool) error {
  466. return nil
  467. }
  468. func (s *mockReferenceStore) AddDigest(ref reference.Canonical, id digest.Digest, force bool) error {
  469. return nil
  470. }
  471. func (s *mockReferenceStore) Delete(ref reference.Named) (bool, error) {
  472. return true, nil
  473. }
  474. func (s *mockReferenceStore) Get(ref reference.Named) (digest.Digest, error) {
  475. return "", nil
  476. }
  477. func TestWhenEmptyAuthConfig(t *testing.T) {
  478. for _, authInfo := range []struct {
  479. username string
  480. password string
  481. registryToken string
  482. expected bool
  483. }{
  484. {
  485. username: "",
  486. password: "",
  487. registryToken: "",
  488. expected: false,
  489. },
  490. {
  491. username: "username",
  492. password: "password",
  493. registryToken: "",
  494. expected: true,
  495. },
  496. {
  497. username: "",
  498. password: "",
  499. registryToken: "token",
  500. expected: true,
  501. },
  502. } {
  503. imagePushConfig := &ImagePushConfig{}
  504. imagePushConfig.AuthConfig = &registry.AuthConfig{
  505. Username: authInfo.username,
  506. Password: authInfo.password,
  507. RegistryToken: authInfo.registryToken,
  508. }
  509. imagePushConfig.ReferenceStore = &mockReferenceStore{}
  510. repoInfo, _ := reference.ParseNormalizedNamed("xujihui1985/test.img")
  511. pusher := &pusher{
  512. config: imagePushConfig,
  513. repoInfo: &registrypkg.RepositoryInfo{
  514. Name: repoInfo,
  515. },
  516. endpoint: registrypkg.APIEndpoint{
  517. URL: &url.URL{
  518. Scheme: "https",
  519. Host: "index.docker.io",
  520. },
  521. Version: registrypkg.APIVersion2,
  522. TrimHostname: true,
  523. },
  524. }
  525. pusher.push(context.Background())
  526. if pusher.pushState.hasAuthInfo != authInfo.expected {
  527. t.Errorf("hasAuthInfo does not match expected: %t != %t", authInfo.expected, pusher.pushState.hasAuthInfo)
  528. }
  529. }
  530. }
  531. type mockBlobStoreWithCreate struct {
  532. mockBlobStore
  533. repo *mockRepoWithBlob
  534. }
  535. func (blob *mockBlobStoreWithCreate) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) {
  536. return nil, errcode.Errors([]error{errcode.ErrorCodeUnauthorized.WithMessage("unauthorized")})
  537. }
  538. type mockRepoWithBlob struct {
  539. mockRepo
  540. }
  541. func (m *mockRepoWithBlob) Blobs(ctx context.Context) distribution.BlobStore {
  542. blob := &mockBlobStoreWithCreate{}
  543. blob.mockBlobStore.repo = &m.mockRepo
  544. blob.repo = m
  545. return blob
  546. }
  547. type mockMetadataService struct {
  548. mockV2MetadataService
  549. }
  550. func (m *mockMetadataService) GetMetadata(diffID layer.DiffID) ([]metadata.V2Metadata, error) {
  551. return []metadata.V2Metadata{
  552. taggedMetadata("abcd", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e28", "docker.io/user/app1"),
  553. taggedMetadata("abcd", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e22", "docker.io/user/app/base"),
  554. taggedMetadata("hash", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e23", "docker.io/user/app"),
  555. taggedMetadata("abcd", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e24", "127.0.0.1/user/app"),
  556. taggedMetadata("hash", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e25", "docker.io/user/foo"),
  557. taggedMetadata("hash", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e26", "docker.io/app/bar"),
  558. }, nil
  559. }
  560. var removeMetadata bool
  561. func (m *mockMetadataService) Remove(metadata metadata.V2Metadata) error {
  562. removeMetadata = true
  563. return nil
  564. }
  565. func TestPushRegistryWhenAuthInfoEmpty(t *testing.T) {
  566. repoInfo, _ := reference.ParseNormalizedNamed("user/app")
  567. ms := &mockMetadataService{}
  568. remoteErrors := map[digest.Digest]error{digest.Digest("sha256:apple"): distribution.ErrAccessDenied}
  569. remoteBlobs := map[digest.Digest]distribution.Descriptor{digest.Digest("sha256:apple"): {Digest: digest.Digest("shar256:apple")}}
  570. repo := &mockRepoWithBlob{
  571. mockRepo: mockRepo{
  572. t: t,
  573. errors: remoteErrors,
  574. blobs: remoteBlobs,
  575. requests: []string{},
  576. },
  577. }
  578. pd := &pushDescriptor{
  579. hmacKey: []byte("abcd"),
  580. repoInfo: repoInfo,
  581. layer: &storeLayer{
  582. Layer: layer.EmptyLayer,
  583. },
  584. repo: repo,
  585. metadataService: ms,
  586. pushState: &pushState{
  587. remoteLayers: make(map[layer.DiffID]distribution.Descriptor),
  588. hasAuthInfo: false,
  589. },
  590. checkedDigests: make(map[digest.Digest]struct{}),
  591. }
  592. pd.Upload(context.Background(), &progressSink{t})
  593. if removeMetadata {
  594. t.Fatalf("expect remove not be called but called")
  595. }
  596. }
  597. func taggedMetadata(key string, dgst string, sourceRepo string) metadata.V2Metadata {
  598. meta := metadata.V2Metadata{
  599. Digest: digest.Digest(dgst),
  600. SourceRepository: sourceRepo,
  601. }
  602. meta.HMAC = metadata.ComputeV2MetadataHMAC([]byte(key), &meta)
  603. return meta
  604. }
  605. type mockRepo struct {
  606. t *testing.T
  607. errors map[digest.Digest]error
  608. blobs map[digest.Digest]distribution.Descriptor
  609. requests []string
  610. }
  611. var _ distribution.Repository = &mockRepo{}
  612. func (m *mockRepo) Named() reference.Named {
  613. m.t.Fatalf("Named() not implemented")
  614. return nil
  615. }
  616. func (m *mockRepo) Manifests(ctc context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
  617. m.t.Fatalf("Manifests() not implemented")
  618. return nil, nil
  619. }
  620. func (m *mockRepo) Tags(ctc context.Context) distribution.TagService {
  621. m.t.Fatalf("Tags() not implemented")
  622. return nil
  623. }
  624. func (m *mockRepo) Blobs(ctx context.Context) distribution.BlobStore {
  625. return &mockBlobStore{
  626. repo: m,
  627. }
  628. }
  629. type mockBlobStore struct {
  630. repo *mockRepo
  631. }
  632. var _ distribution.BlobStore = &mockBlobStore{}
  633. func (m *mockBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
  634. m.repo.requests = append(m.repo.requests, dgst.String())
  635. if err, exists := m.repo.errors[dgst]; exists {
  636. return distribution.Descriptor{}, err
  637. }
  638. if desc, exists := m.repo.blobs[dgst]; exists {
  639. return desc, nil
  640. }
  641. return distribution.Descriptor{}, distribution.ErrBlobUnknown
  642. }
  643. func (m *mockBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
  644. m.repo.t.Fatal("Get() not implemented")
  645. return nil, nil
  646. }
  647. func (m *mockBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
  648. m.repo.t.Fatal("Open() not implemented")
  649. return nil, nil
  650. }
  651. func (m *mockBlobStore) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) {
  652. m.repo.t.Fatal("Put() not implemented")
  653. return distribution.Descriptor{}, nil
  654. }
  655. func (m *mockBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) {
  656. m.repo.t.Fatal("Create() not implemented")
  657. return nil, nil
  658. }
  659. func (m *mockBlobStore) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) {
  660. m.repo.t.Fatal("Resume() not implemented")
  661. return nil, nil
  662. }
  663. func (m *mockBlobStore) Delete(ctx context.Context, dgst digest.Digest) error {
  664. m.repo.t.Fatal("Delete() not implemented")
  665. return nil
  666. }
  667. func (m *mockBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error {
  668. m.repo.t.Fatalf("ServeBlob() not implemented")
  669. return nil
  670. }
  671. type mockV2MetadataService struct {
  672. added []metadata.V2Metadata
  673. removed []metadata.V2Metadata
  674. }
  675. var _ metadata.V2MetadataService = &mockV2MetadataService{}
  676. func (*mockV2MetadataService) GetMetadata(diffID layer.DiffID) ([]metadata.V2Metadata, error) {
  677. return nil, nil
  678. }
  679. func (*mockV2MetadataService) GetDiffID(dgst digest.Digest) (layer.DiffID, error) {
  680. return "", nil
  681. }
  682. func (m *mockV2MetadataService) Add(diffID layer.DiffID, metadata metadata.V2Metadata) error {
  683. m.added = append(m.added, metadata)
  684. return nil
  685. }
  686. func (m *mockV2MetadataService) TagAndAdd(diffID layer.DiffID, hmacKey []byte, meta metadata.V2Metadata) error {
  687. meta.HMAC = metadata.ComputeV2MetadataHMAC(hmacKey, &meta)
  688. m.Add(diffID, meta)
  689. return nil
  690. }
  691. func (m *mockV2MetadataService) Remove(metadata metadata.V2Metadata) error {
  692. m.removed = append(m.removed, metadata)
  693. return nil
  694. }
  695. type progressSink struct {
  696. t *testing.T
  697. }
  698. func (s *progressSink) WriteProgress(p progress.Progress) error {
  699. s.t.Logf("progress update: %#+v", p)
  700. return nil
  701. }