push_v2_test.go 26 KB

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