...

Source file src/cuelabs.dev/go/oci/ociregistry/ociserver/reader.go

Documentation: cuelabs.dev/go/oci/ociregistry/ociserver

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ociserver
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"net/http"
    22  
    23  	"cuelabs.dev/go/oci/ociregistry"
    24  	"cuelabs.dev/go/oci/ociregistry/internal/ocirequest"
    25  )
    26  
    27  func (r *registry) handleBlobHead(ctx context.Context, resp http.ResponseWriter, req *http.Request, rreq *ocirequest.Request) error {
    28  	desc, err := r.backend.ResolveBlob(ctx, rreq.Repo, ociregistry.Digest(rreq.Digest))
    29  	if err != nil {
    30  		return err
    31  	}
    32  	resp.Header().Set("Content-Length", fmt.Sprint(desc.Size))
    33  	resp.Header().Set("Docker-Content-Digest", string(desc.Digest))
    34  	// TODO this is true in theory, but what if the backend doesn't support GetBlobRange ?
    35  	resp.Header().Set("Accept-Ranges", "bytes")
    36  	resp.WriteHeader(http.StatusOK)
    37  	return nil
    38  }
    39  
    40  func (r *registry) handleBlobGet(ctx context.Context, resp http.ResponseWriter, req *http.Request, rreq *ocirequest.Request) error {
    41  	if r.opts.LocationsForDescriptor != nil {
    42  		// We need to find information on the blob before we can determine
    43  		// what to pass back, so resolve the blob first so we don't
    44  		// stimulate the backend to start sending the whole stream
    45  		// only to abandon it.
    46  		desc, err := r.backend.ResolveBlob(ctx, rreq.Repo, ociregistry.Digest(rreq.Digest))
    47  		if err != nil {
    48  			// TODO this might not be the best response because ResolveBlob is
    49  			// often implemented with a HEAD request that can't return an error
    50  			// body. So it might be better to fall through to the usual GetBlob request,
    51  			// although that would mean that every error makes two calls :(
    52  			return err
    53  		}
    54  		locs, err := r.opts.LocationsForDescriptor(false, desc)
    55  		if err != nil {
    56  			return err
    57  		}
    58  		if len(locs) > 0 {
    59  			// TODO choose randomly from the set of locations?
    60  			// TODO make it possible to turn off this behaviour?
    61  			http.Redirect(resp, req, locs[0], http.StatusTemporaryRedirect)
    62  			return nil
    63  		}
    64  	}
    65  	ranges, err := parseRange(req.Header.Get("Range"))
    66  	if err != nil {
    67  		return withHTTPCode(http.StatusRequestedRangeNotSatisfiable, err)
    68  	}
    69  	switch len(ranges) {
    70  	case 0:
    71  		blob, err := r.backend.GetBlob(ctx, rreq.Repo, ociregistry.Digest(rreq.Digest))
    72  		if err != nil {
    73  			return err
    74  		}
    75  		defer blob.Close()
    76  		desc := blob.Descriptor()
    77  		resp.Header().Set("Content-Type", desc.MediaType)
    78  		resp.Header().Set("Content-Length", fmt.Sprint(desc.Size))
    79  		resp.Header().Set("Docker-Content-Digest", rreq.Digest)
    80  		resp.WriteHeader(http.StatusOK)
    81  
    82  		io.Copy(resp, blob)
    83  		return nil
    84  	case 1:
    85  		rng := ranges[0]
    86  		blob, err := r.backend.GetBlobRange(ctx, rreq.Repo, ociregistry.Digest(rreq.Digest), rng.start, rng.end)
    87  		if err != nil {
    88  			// TODO fall back to using GetBlob if err is ErrUnsupported?
    89  			return err
    90  		}
    91  		defer blob.Close()
    92  		desc := blob.Descriptor()
    93  		if rng.end == -1 || rng.end > desc.Size {
    94  			rng.end = desc.Size
    95  		}
    96  		if rng.start > desc.Size {
    97  			return withHTTPCode(http.StatusRequestedRangeNotSatisfiable, fmt.Errorf("range starts after end of blob"))
    98  		}
    99  		if rng.end < rng.start {
   100  			return withHTTPCode(http.StatusRequestedRangeNotSatisfiable, fmt.Errorf("range end is before start"))
   101  		}
   102  		resp.Header().Set("Content-Type", desc.MediaType)
   103  		resp.Header().Set("Content-Length", fmt.Sprint(rng.end-rng.start))
   104  		resp.Header().Set("Docker-Content-Digest", rreq.Digest)
   105  		resp.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", rng.start, rng.end-1, desc.Size))
   106  		resp.WriteHeader(http.StatusPartialContent)
   107  
   108  		io.Copy(resp, blob)
   109  		return nil
   110  
   111  	default:
   112  		return withHTTPCode(http.StatusRequestedRangeNotSatisfiable, fmt.Errorf("only a single range is supported"))
   113  	}
   114  }
   115  
   116  func (r *registry) handleManifestGet(ctx context.Context, resp http.ResponseWriter, req *http.Request, rreq *ocirequest.Request) error {
   117  	// TODO we could do a redirect here too if we thought it was worthwhile.
   118  	var mr ociregistry.BlobReader
   119  	var err error
   120  	if rreq.Tag != "" {
   121  		mr, err = r.backend.GetTag(ctx, rreq.Repo, rreq.Tag)
   122  	} else {
   123  		mr, err = r.backend.GetManifest(ctx, rreq.Repo, ociregistry.Digest(rreq.Digest))
   124  	}
   125  	if err != nil {
   126  		return err
   127  	}
   128  	desc := mr.Descriptor()
   129  	if !r.opts.OmitDigestFromTagGetResponse {
   130  		resp.Header().Set("Docker-Content-Digest", string(desc.Digest))
   131  	}
   132  	resp.Header().Set("Content-Type", desc.MediaType)
   133  	resp.Header().Set("Content-Length", fmt.Sprint(desc.Size))
   134  	resp.WriteHeader(http.StatusOK)
   135  	io.Copy(resp, mr)
   136  	return nil
   137  }
   138  
   139  func (r *registry) handleManifestHead(ctx context.Context, resp http.ResponseWriter, req *http.Request, rreq *ocirequest.Request) error {
   140  	var desc ociregistry.Descriptor
   141  	var err error
   142  	if rreq.Tag != "" {
   143  		desc, err = r.backend.ResolveTag(ctx, rreq.Repo, rreq.Tag)
   144  	} else {
   145  		desc, err = r.backend.ResolveManifest(ctx, rreq.Repo, ociregistry.Digest(rreq.Digest))
   146  	}
   147  	if err != nil {
   148  		return err
   149  	}
   150  	resp.Header().Set("Docker-Content-Digest", string(desc.Digest))
   151  	resp.Header().Set("Content-Type", desc.MediaType)
   152  	resp.Header().Set("Content-Length", fmt.Sprint(desc.Size))
   153  	resp.WriteHeader(http.StatusOK)
   154  	return nil
   155  }
   156  

View as plain text