repository.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  1. package client
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "io/ioutil"
  10. "net/http"
  11. "net/url"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "github.com/distribution/reference"
  16. "github.com/docker/distribution"
  17. v2 "github.com/docker/distribution/registry/api/v2"
  18. "github.com/docker/distribution/registry/client/transport"
  19. "github.com/docker/distribution/registry/storage/cache"
  20. "github.com/docker/distribution/registry/storage/cache/memory"
  21. "github.com/opencontainers/go-digest"
  22. )
  23. // Registry provides an interface for calling Repositories, which returns a catalog of repositories.
  24. type Registry interface {
  25. Repositories(ctx context.Context, repos []string, last string) (n int, err error)
  26. }
  27. // checkHTTPRedirect is a callback that can manipulate redirected HTTP
  28. // requests. It is used to preserve Accept and Range headers.
  29. func checkHTTPRedirect(req *http.Request, via []*http.Request) error {
  30. if len(via) >= 10 {
  31. return errors.New("stopped after 10 redirects")
  32. }
  33. if len(via) > 0 {
  34. for headerName, headerVals := range via[0].Header {
  35. if headerName != "Accept" && headerName != "Range" {
  36. continue
  37. }
  38. for _, val := range headerVals {
  39. // Don't add to redirected request if redirected
  40. // request already has a header with the same
  41. // name and value.
  42. hasValue := false
  43. for _, existingVal := range req.Header[headerName] {
  44. if existingVal == val {
  45. hasValue = true
  46. break
  47. }
  48. }
  49. if !hasValue {
  50. req.Header.Add(headerName, val)
  51. }
  52. }
  53. }
  54. }
  55. return nil
  56. }
  57. // NewRegistry creates a registry namespace which can be used to get a listing of repositories
  58. func NewRegistry(baseURL string, transport http.RoundTripper) (Registry, error) {
  59. ub, err := v2.NewURLBuilderFromString(baseURL, false)
  60. if err != nil {
  61. return nil, err
  62. }
  63. client := &http.Client{
  64. Transport: transport,
  65. Timeout: 1 * time.Minute,
  66. CheckRedirect: checkHTTPRedirect,
  67. }
  68. return &registry{
  69. client: client,
  70. ub: ub,
  71. }, nil
  72. }
  73. type registry struct {
  74. client *http.Client
  75. ub *v2.URLBuilder
  76. }
  77. // Repositories returns a lexigraphically sorted catalog given a base URL. The 'entries' slice will be filled up to the size
  78. // of the slice, starting at the value provided in 'last'. The number of entries will be returned along with io.EOF if there
  79. // are no more entries
  80. func (r *registry) Repositories(ctx context.Context, entries []string, last string) (int, error) {
  81. var numFilled int
  82. var returnErr error
  83. values := buildCatalogValues(len(entries), last)
  84. u, err := r.ub.BuildCatalogURL(values)
  85. if err != nil {
  86. return 0, err
  87. }
  88. resp, err := r.client.Get(u)
  89. if err != nil {
  90. return 0, err
  91. }
  92. defer resp.Body.Close()
  93. if SuccessStatus(resp.StatusCode) {
  94. var ctlg struct {
  95. Repositories []string `json:"repositories"`
  96. }
  97. decoder := json.NewDecoder(resp.Body)
  98. if err := decoder.Decode(&ctlg); err != nil {
  99. return 0, err
  100. }
  101. copy(entries, ctlg.Repositories)
  102. numFilled = len(ctlg.Repositories)
  103. link := resp.Header.Get("Link")
  104. if link == "" {
  105. returnErr = io.EOF
  106. }
  107. } else {
  108. return 0, HandleErrorResponse(resp)
  109. }
  110. return numFilled, returnErr
  111. }
  112. // NewRepository creates a new Repository for the given repository name and base URL.
  113. func NewRepository(name reference.Named, baseURL string, transport http.RoundTripper) (distribution.Repository, error) {
  114. ub, err := v2.NewURLBuilderFromString(baseURL, false)
  115. if err != nil {
  116. return nil, err
  117. }
  118. client := &http.Client{
  119. Transport: transport,
  120. CheckRedirect: checkHTTPRedirect,
  121. // TODO(dmcgowan): create cookie jar
  122. }
  123. return &repository{
  124. client: client,
  125. ub: ub,
  126. name: name,
  127. }, nil
  128. }
  129. type repository struct {
  130. client *http.Client
  131. ub *v2.URLBuilder
  132. name reference.Named
  133. }
  134. func (r *repository) Named() reference.Named {
  135. return r.name
  136. }
  137. func (r *repository) Blobs(ctx context.Context) distribution.BlobStore {
  138. statter := &blobStatter{
  139. name: r.name,
  140. ub: r.ub,
  141. client: r.client,
  142. }
  143. return &blobs{
  144. name: r.name,
  145. ub: r.ub,
  146. client: r.client,
  147. statter: cache.NewCachedBlobStatter(memory.NewInMemoryBlobDescriptorCacheProvider(), statter),
  148. }
  149. }
  150. func (r *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
  151. // todo(richardscothern): options should be sent over the wire
  152. return &manifests{
  153. name: r.name,
  154. ub: r.ub,
  155. client: r.client,
  156. etags: make(map[string]string),
  157. }, nil
  158. }
  159. func (r *repository) Tags(ctx context.Context) distribution.TagService {
  160. return &tags{
  161. client: r.client,
  162. ub: r.ub,
  163. name: r.Named(),
  164. }
  165. }
  166. // tags implements remote tagging operations.
  167. type tags struct {
  168. client *http.Client
  169. ub *v2.URLBuilder
  170. name reference.Named
  171. }
  172. // All returns all tags
  173. func (t *tags) All(ctx context.Context) ([]string, error) {
  174. var tags []string
  175. listURLStr, err := t.ub.BuildTagsURL(t.name)
  176. if err != nil {
  177. return tags, err
  178. }
  179. listURL, err := url.Parse(listURLStr)
  180. if err != nil {
  181. return tags, err
  182. }
  183. for {
  184. resp, err := t.client.Get(listURL.String())
  185. if err != nil {
  186. return tags, err
  187. }
  188. defer resp.Body.Close()
  189. if SuccessStatus(resp.StatusCode) {
  190. b, err := ioutil.ReadAll(resp.Body)
  191. if err != nil {
  192. return tags, err
  193. }
  194. tagsResponse := struct {
  195. Tags []string `json:"tags"`
  196. }{}
  197. if err := json.Unmarshal(b, &tagsResponse); err != nil {
  198. return tags, err
  199. }
  200. tags = append(tags, tagsResponse.Tags...)
  201. if link := resp.Header.Get("Link"); link != "" {
  202. linkURLStr := strings.Trim(strings.Split(link, ";")[0], "<>")
  203. linkURL, err := url.Parse(linkURLStr)
  204. if err != nil {
  205. return tags, err
  206. }
  207. listURL = listURL.ResolveReference(linkURL)
  208. } else {
  209. return tags, nil
  210. }
  211. } else {
  212. return tags, HandleErrorResponse(resp)
  213. }
  214. }
  215. }
  216. func descriptorFromResponse(response *http.Response) (distribution.Descriptor, error) {
  217. desc := distribution.Descriptor{}
  218. headers := response.Header
  219. ctHeader := headers.Get("Content-Type")
  220. if ctHeader == "" {
  221. return distribution.Descriptor{}, errors.New("missing or empty Content-Type header")
  222. }
  223. desc.MediaType = ctHeader
  224. digestHeader := headers.Get("Docker-Content-Digest")
  225. if digestHeader == "" {
  226. bytes, err := ioutil.ReadAll(response.Body)
  227. if err != nil {
  228. return distribution.Descriptor{}, err
  229. }
  230. _, desc, err := distribution.UnmarshalManifest(ctHeader, bytes)
  231. if err != nil {
  232. return distribution.Descriptor{}, err
  233. }
  234. return desc, nil
  235. }
  236. dgst, err := digest.Parse(digestHeader)
  237. if err != nil {
  238. return distribution.Descriptor{}, err
  239. }
  240. desc.Digest = dgst
  241. lengthHeader := headers.Get("Content-Length")
  242. if lengthHeader == "" {
  243. return distribution.Descriptor{}, errors.New("missing or empty Content-Length header")
  244. }
  245. length, err := strconv.ParseInt(lengthHeader, 10, 64)
  246. if err != nil {
  247. return distribution.Descriptor{}, err
  248. }
  249. desc.Size = length
  250. return desc, nil
  251. }
  252. // Get issues a HEAD request for a Manifest against its named endpoint in order
  253. // to construct a descriptor for the tag. If the registry doesn't support HEADing
  254. // a manifest, fallback to GET.
  255. func (t *tags) Get(ctx context.Context, tag string) (distribution.Descriptor, error) {
  256. ref, err := reference.WithTag(t.name, tag)
  257. if err != nil {
  258. return distribution.Descriptor{}, err
  259. }
  260. u, err := t.ub.BuildManifestURL(ref)
  261. if err != nil {
  262. return distribution.Descriptor{}, err
  263. }
  264. newRequest := func(method string) (*http.Response, error) {
  265. req, err := http.NewRequest(method, u, nil)
  266. if err != nil {
  267. return nil, err
  268. }
  269. for _, t := range distribution.ManifestMediaTypes() {
  270. req.Header.Add("Accept", t)
  271. }
  272. resp, err := t.client.Do(req)
  273. return resp, err
  274. }
  275. resp, err := newRequest("HEAD")
  276. if err != nil {
  277. return distribution.Descriptor{}, err
  278. }
  279. defer resp.Body.Close()
  280. switch {
  281. case resp.StatusCode >= 200 && resp.StatusCode < 400 && len(resp.Header.Get("Docker-Content-Digest")) > 0:
  282. // if the response is a success AND a Docker-Content-Digest can be retrieved from the headers
  283. return descriptorFromResponse(resp)
  284. default:
  285. // if the response is an error - there will be no body to decode.
  286. // Issue a GET request:
  287. // - for data from a server that does not handle HEAD
  288. // - to get error details in case of a failure
  289. resp, err = newRequest("GET")
  290. if err != nil {
  291. return distribution.Descriptor{}, err
  292. }
  293. defer resp.Body.Close()
  294. if resp.StatusCode >= 200 && resp.StatusCode < 400 {
  295. return descriptorFromResponse(resp)
  296. }
  297. return distribution.Descriptor{}, HandleErrorResponse(resp)
  298. }
  299. }
  300. func (t *tags) Lookup(ctx context.Context, digest distribution.Descriptor) ([]string, error) {
  301. panic("not implemented")
  302. }
  303. func (t *tags) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error {
  304. panic("not implemented")
  305. }
  306. func (t *tags) Untag(ctx context.Context, tag string) error {
  307. panic("not implemented")
  308. }
  309. type manifests struct {
  310. name reference.Named
  311. ub *v2.URLBuilder
  312. client *http.Client
  313. etags map[string]string
  314. }
  315. func (ms *manifests) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
  316. ref, err := reference.WithDigest(ms.name, dgst)
  317. if err != nil {
  318. return false, err
  319. }
  320. u, err := ms.ub.BuildManifestURL(ref)
  321. if err != nil {
  322. return false, err
  323. }
  324. resp, err := ms.client.Head(u)
  325. if err != nil {
  326. return false, err
  327. }
  328. if SuccessStatus(resp.StatusCode) {
  329. return true, nil
  330. } else if resp.StatusCode == http.StatusNotFound {
  331. return false, nil
  332. }
  333. return false, HandleErrorResponse(resp)
  334. }
  335. // AddEtagToTag allows a client to supply an eTag to Get which will be
  336. // used for a conditional HTTP request. If the eTag matches, a nil manifest
  337. // and ErrManifestNotModified error will be returned. etag is automatically
  338. // quoted when added to this map.
  339. func AddEtagToTag(tag, etag string) distribution.ManifestServiceOption {
  340. return etagOption{tag, etag}
  341. }
  342. type etagOption struct{ tag, etag string }
  343. func (o etagOption) Apply(ms distribution.ManifestService) error {
  344. if ms, ok := ms.(*manifests); ok {
  345. ms.etags[o.tag] = fmt.Sprintf(`"%s"`, o.etag)
  346. return nil
  347. }
  348. return fmt.Errorf("etag options is a client-only option")
  349. }
  350. // ReturnContentDigest allows a client to set a the content digest on
  351. // a successful request from the 'Docker-Content-Digest' header. This
  352. // returned digest is represents the digest which the registry uses
  353. // to refer to the content and can be used to delete the content.
  354. func ReturnContentDigest(dgst *digest.Digest) distribution.ManifestServiceOption {
  355. return contentDigestOption{dgst}
  356. }
  357. type contentDigestOption struct{ digest *digest.Digest }
  358. func (o contentDigestOption) Apply(ms distribution.ManifestService) error {
  359. return nil
  360. }
  361. func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
  362. var (
  363. digestOrTag string
  364. ref reference.Named
  365. err error
  366. contentDgst *digest.Digest
  367. mediaTypes []string
  368. )
  369. for _, option := range options {
  370. switch opt := option.(type) {
  371. case distribution.WithTagOption:
  372. digestOrTag = opt.Tag
  373. ref, err = reference.WithTag(ms.name, opt.Tag)
  374. if err != nil {
  375. return nil, err
  376. }
  377. case contentDigestOption:
  378. contentDgst = opt.digest
  379. case distribution.WithManifestMediaTypesOption:
  380. mediaTypes = opt.MediaTypes
  381. default:
  382. err := option.Apply(ms)
  383. if err != nil {
  384. return nil, err
  385. }
  386. }
  387. }
  388. if digestOrTag == "" {
  389. digestOrTag = dgst.String()
  390. ref, err = reference.WithDigest(ms.name, dgst)
  391. if err != nil {
  392. return nil, err
  393. }
  394. }
  395. if len(mediaTypes) == 0 {
  396. mediaTypes = distribution.ManifestMediaTypes()
  397. }
  398. u, err := ms.ub.BuildManifestURL(ref)
  399. if err != nil {
  400. return nil, err
  401. }
  402. req, err := http.NewRequest("GET", u, nil)
  403. if err != nil {
  404. return nil, err
  405. }
  406. for _, t := range mediaTypes {
  407. req.Header.Add("Accept", t)
  408. }
  409. if _, ok := ms.etags[digestOrTag]; ok {
  410. req.Header.Set("If-None-Match", ms.etags[digestOrTag])
  411. }
  412. resp, err := ms.client.Do(req)
  413. if err != nil {
  414. return nil, err
  415. }
  416. defer resp.Body.Close()
  417. if resp.StatusCode == http.StatusNotModified {
  418. return nil, distribution.ErrManifestNotModified
  419. } else if SuccessStatus(resp.StatusCode) {
  420. if contentDgst != nil {
  421. dgst, err := digest.Parse(resp.Header.Get("Docker-Content-Digest"))
  422. if err == nil {
  423. *contentDgst = dgst
  424. }
  425. }
  426. mt := resp.Header.Get("Content-Type")
  427. body, err := ioutil.ReadAll(resp.Body)
  428. if err != nil {
  429. return nil, err
  430. }
  431. m, _, err := distribution.UnmarshalManifest(mt, body)
  432. if err != nil {
  433. return nil, err
  434. }
  435. return m, nil
  436. }
  437. return nil, HandleErrorResponse(resp)
  438. }
  439. // Put puts a manifest. A tag can be specified using an options parameter which uses some shared state to hold the
  440. // tag name in order to build the correct upload URL.
  441. func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
  442. ref := ms.name
  443. var tagged bool
  444. for _, option := range options {
  445. if opt, ok := option.(distribution.WithTagOption); ok {
  446. var err error
  447. ref, err = reference.WithTag(ref, opt.Tag)
  448. if err != nil {
  449. return "", err
  450. }
  451. tagged = true
  452. } else {
  453. err := option.Apply(ms)
  454. if err != nil {
  455. return "", err
  456. }
  457. }
  458. }
  459. mediaType, p, err := m.Payload()
  460. if err != nil {
  461. return "", err
  462. }
  463. if !tagged {
  464. // generate a canonical digest and Put by digest
  465. _, d, err := distribution.UnmarshalManifest(mediaType, p)
  466. if err != nil {
  467. return "", err
  468. }
  469. ref, err = reference.WithDigest(ref, d.Digest)
  470. if err != nil {
  471. return "", err
  472. }
  473. }
  474. manifestURL, err := ms.ub.BuildManifestURL(ref)
  475. if err != nil {
  476. return "", err
  477. }
  478. putRequest, err := http.NewRequest("PUT", manifestURL, bytes.NewReader(p))
  479. if err != nil {
  480. return "", err
  481. }
  482. putRequest.Header.Set("Content-Type", mediaType)
  483. resp, err := ms.client.Do(putRequest)
  484. if err != nil {
  485. return "", err
  486. }
  487. defer resp.Body.Close()
  488. if SuccessStatus(resp.StatusCode) {
  489. dgstHeader := resp.Header.Get("Docker-Content-Digest")
  490. dgst, err := digest.Parse(dgstHeader)
  491. if err != nil {
  492. return "", err
  493. }
  494. return dgst, nil
  495. }
  496. return "", HandleErrorResponse(resp)
  497. }
  498. func (ms *manifests) Delete(ctx context.Context, dgst digest.Digest) error {
  499. ref, err := reference.WithDigest(ms.name, dgst)
  500. if err != nil {
  501. return err
  502. }
  503. u, err := ms.ub.BuildManifestURL(ref)
  504. if err != nil {
  505. return err
  506. }
  507. req, err := http.NewRequest("DELETE", u, nil)
  508. if err != nil {
  509. return err
  510. }
  511. resp, err := ms.client.Do(req)
  512. if err != nil {
  513. return err
  514. }
  515. defer resp.Body.Close()
  516. if SuccessStatus(resp.StatusCode) {
  517. return nil
  518. }
  519. return HandleErrorResponse(resp)
  520. }
  521. // todo(richardscothern): Restore interface and implementation with merge of #1050
  522. /*func (ms *manifests) Enumerate(ctx context.Context, manifests []distribution.Manifest, last distribution.Manifest) (n int, err error) {
  523. panic("not supported")
  524. }*/
  525. type blobs struct {
  526. name reference.Named
  527. ub *v2.URLBuilder
  528. client *http.Client
  529. statter distribution.BlobDescriptorService
  530. distribution.BlobDeleter
  531. }
  532. func sanitizeLocation(location, base string) (string, error) {
  533. baseURL, err := url.Parse(base)
  534. if err != nil {
  535. return "", err
  536. }
  537. locationURL, err := url.Parse(location)
  538. if err != nil {
  539. return "", err
  540. }
  541. return baseURL.ResolveReference(locationURL).String(), nil
  542. }
  543. func (bs *blobs) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
  544. return bs.statter.Stat(ctx, dgst)
  545. }
  546. func (bs *blobs) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
  547. reader, err := bs.Open(ctx, dgst)
  548. if err != nil {
  549. return nil, err
  550. }
  551. defer reader.Close()
  552. return ioutil.ReadAll(reader)
  553. }
  554. func (bs *blobs) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
  555. ref, err := reference.WithDigest(bs.name, dgst)
  556. if err != nil {
  557. return nil, err
  558. }
  559. blobURL, err := bs.ub.BuildBlobURL(ref)
  560. if err != nil {
  561. return nil, err
  562. }
  563. return transport.NewHTTPReadSeeker(bs.client, blobURL,
  564. func(resp *http.Response) error {
  565. if resp.StatusCode == http.StatusNotFound {
  566. return distribution.ErrBlobUnknown
  567. }
  568. return HandleErrorResponse(resp)
  569. }), nil
  570. }
  571. func (bs *blobs) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error {
  572. panic("not implemented")
  573. }
  574. func (bs *blobs) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) {
  575. writer, err := bs.Create(ctx)
  576. if err != nil {
  577. return distribution.Descriptor{}, err
  578. }
  579. dgstr := digest.Canonical.Digester()
  580. n, err := io.Copy(writer, io.TeeReader(bytes.NewReader(p), dgstr.Hash()))
  581. if err != nil {
  582. return distribution.Descriptor{}, err
  583. }
  584. if n < int64(len(p)) {
  585. return distribution.Descriptor{}, fmt.Errorf("short copy: wrote %d of %d", n, len(p))
  586. }
  587. desc := distribution.Descriptor{
  588. MediaType: mediaType,
  589. Size: int64(len(p)),
  590. Digest: dgstr.Digest(),
  591. }
  592. return writer.Commit(ctx, desc)
  593. }
  594. type optionFunc func(interface{}) error
  595. func (f optionFunc) Apply(v interface{}) error {
  596. return f(v)
  597. }
  598. // WithMountFrom returns a BlobCreateOption which designates that the blob should be
  599. // mounted from the given canonical reference.
  600. func WithMountFrom(ref reference.Canonical) distribution.BlobCreateOption {
  601. return optionFunc(func(v interface{}) error {
  602. opts, ok := v.(*distribution.CreateOptions)
  603. if !ok {
  604. return fmt.Errorf("unexpected options type: %T", v)
  605. }
  606. opts.Mount.ShouldMount = true
  607. opts.Mount.From = ref
  608. return nil
  609. })
  610. }
  611. func (bs *blobs) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) {
  612. var opts distribution.CreateOptions
  613. for _, option := range options {
  614. err := option.Apply(&opts)
  615. if err != nil {
  616. return nil, err
  617. }
  618. }
  619. var values []url.Values
  620. if opts.Mount.ShouldMount {
  621. values = append(values, url.Values{"from": {opts.Mount.From.Name()}, "mount": {opts.Mount.From.Digest().String()}})
  622. }
  623. u, err := bs.ub.BuildBlobUploadURL(bs.name, values...)
  624. if err != nil {
  625. return nil, err
  626. }
  627. req, err := http.NewRequest("POST", u, nil)
  628. if err != nil {
  629. return nil, err
  630. }
  631. resp, err := bs.client.Do(req)
  632. if err != nil {
  633. return nil, err
  634. }
  635. defer resp.Body.Close()
  636. switch resp.StatusCode {
  637. case http.StatusCreated:
  638. desc, err := bs.statter.Stat(ctx, opts.Mount.From.Digest())
  639. if err != nil {
  640. return nil, err
  641. }
  642. return nil, distribution.ErrBlobMounted{From: opts.Mount.From, Descriptor: desc}
  643. case http.StatusAccepted:
  644. // TODO(dmcgowan): Check for invalid UUID
  645. uuid := resp.Header.Get("Docker-Upload-UUID")
  646. location, err := sanitizeLocation(resp.Header.Get("Location"), u)
  647. if err != nil {
  648. return nil, err
  649. }
  650. return &httpBlobUpload{
  651. statter: bs.statter,
  652. client: bs.client,
  653. uuid: uuid,
  654. startedAt: time.Now(),
  655. location: location,
  656. }, nil
  657. default:
  658. return nil, HandleErrorResponse(resp)
  659. }
  660. }
  661. func (bs *blobs) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) {
  662. panic("not implemented")
  663. }
  664. func (bs *blobs) Delete(ctx context.Context, dgst digest.Digest) error {
  665. return bs.statter.Clear(ctx, dgst)
  666. }
  667. type blobStatter struct {
  668. name reference.Named
  669. ub *v2.URLBuilder
  670. client *http.Client
  671. }
  672. func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
  673. ref, err := reference.WithDigest(bs.name, dgst)
  674. if err != nil {
  675. return distribution.Descriptor{}, err
  676. }
  677. u, err := bs.ub.BuildBlobURL(ref)
  678. if err != nil {
  679. return distribution.Descriptor{}, err
  680. }
  681. resp, err := bs.client.Head(u)
  682. if err != nil {
  683. return distribution.Descriptor{}, err
  684. }
  685. defer resp.Body.Close()
  686. if SuccessStatus(resp.StatusCode) {
  687. lengthHeader := resp.Header.Get("Content-Length")
  688. if lengthHeader == "" {
  689. return distribution.Descriptor{}, fmt.Errorf("missing content-length header for request: %s", u)
  690. }
  691. length, err := strconv.ParseInt(lengthHeader, 10, 64)
  692. if err != nil {
  693. return distribution.Descriptor{}, fmt.Errorf("error parsing content-length: %v", err)
  694. }
  695. return distribution.Descriptor{
  696. MediaType: resp.Header.Get("Content-Type"),
  697. Size: length,
  698. Digest: dgst,
  699. }, nil
  700. } else if resp.StatusCode == http.StatusNotFound {
  701. return distribution.Descriptor{}, distribution.ErrBlobUnknown
  702. }
  703. return distribution.Descriptor{}, HandleErrorResponse(resp)
  704. }
  705. func buildCatalogValues(maxEntries int, last string) url.Values {
  706. values := url.Values{}
  707. if maxEntries > 0 {
  708. values.Add("n", strconv.Itoa(maxEntries))
  709. }
  710. if last != "" {
  711. values.Add("last", last)
  712. }
  713. return values
  714. }
  715. func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
  716. ref, err := reference.WithDigest(bs.name, dgst)
  717. if err != nil {
  718. return err
  719. }
  720. blobURL, err := bs.ub.BuildBlobURL(ref)
  721. if err != nil {
  722. return err
  723. }
  724. req, err := http.NewRequest("DELETE", blobURL, nil)
  725. if err != nil {
  726. return err
  727. }
  728. resp, err := bs.client.Do(req)
  729. if err != nil {
  730. return err
  731. }
  732. defer resp.Body.Close()
  733. if SuccessStatus(resp.StatusCode) {
  734. return nil
  735. }
  736. return HandleErrorResponse(resp)
  737. }
  738. func (bs *blobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
  739. return nil
  740. }