repository.go 21 KB

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