2019-06-20 16:36:40 -07:00
// @title Open Container Initiative Distribution Specification
// @version v0.1.0-dev
// @description APIs for Open Container Initiative Distribution Specification
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
package api
import (
"fmt"
2019-07-09 22:23:59 -07:00
"io"
"io/ioutil"
2019-06-20 16:36:40 -07:00
"net/http"
"path"
2020-01-09 16:31:34 -08:00
"sort"
2019-06-20 16:36:40 -07:00
"strconv"
"strings"
_ "github.com/anuvu/zot/docs" // nolint (golint) - as required by swaggo
"github.com/anuvu/zot/errors"
2020-01-04 18:20:06 -08:00
"github.com/anuvu/zot/pkg/log"
2019-07-09 22:23:59 -07:00
"github.com/gorilla/mux"
jsoniter "github.com/json-iterator/go"
2019-06-20 16:36:40 -07:00
ispec "github.com/opencontainers/image-spec/specs-go/v1"
2019-07-09 22:23:59 -07:00
httpSwagger "github.com/swaggo/http-swagger"
2019-06-20 16:36:40 -07:00
)
2019-10-09 11:50:10 -07:00
const (
RoutePrefix = "/v2"
DistAPIVersion = "Docker-Distribution-API-Version"
DistContentDigestKey = "Docker-Content-Digest"
BlobUploadUUID = "Blob-Upload-UUID"
DefaultMediaType = "application/json"
2020-01-09 16:31:34 -08:00
BinaryMediaType = "application/octet-stream"
2019-10-09 11:50:10 -07:00
)
2019-06-20 16:36:40 -07:00
type RouteHandler struct {
c * Controller
}
func NewRouteHandler ( c * Controller ) * RouteHandler {
rh := & RouteHandler { c : c }
rh . SetupRoutes ( )
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return rh
}
func ( rh * RouteHandler ) SetupRoutes ( ) {
2020-01-24 15:32:38 -06:00
rh . c . Router . Use ( AuthHandler ( rh . c ) )
2019-07-09 22:23:59 -07:00
g := rh . c . Router . PathPrefix ( RoutePrefix ) . Subrouter ( )
2019-06-20 16:36:40 -07:00
{
2019-07-09 22:23:59 -07:00
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/tags/list" , NameRegexp . String ( ) ) ,
rh . ListTags ) . Methods ( "GET" )
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/manifests/{reference}" , NameRegexp . String ( ) ) ,
rh . CheckManifest ) . Methods ( "HEAD" )
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/manifests/{reference}" , NameRegexp . String ( ) ) ,
rh . GetManifest ) . Methods ( "GET" )
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/manifests/{reference}" , NameRegexp . String ( ) ) ,
rh . UpdateManifest ) . Methods ( "PUT" )
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/manifests/{reference}" , NameRegexp . String ( ) ) ,
rh . DeleteManifest ) . Methods ( "DELETE" )
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/blobs/{digest}" , NameRegexp . String ( ) ) ,
rh . CheckBlob ) . Methods ( "HEAD" )
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/blobs/{digest}" , NameRegexp . String ( ) ) ,
rh . GetBlob ) . Methods ( "GET" )
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/blobs/{digest}" , NameRegexp . String ( ) ) ,
rh . DeleteBlob ) . Methods ( "DELETE" )
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/blobs/uploads/" , NameRegexp . String ( ) ) ,
rh . CreateBlobUpload ) . Methods ( "POST" )
2020-01-09 16:31:34 -08:00
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/blobs/uploads/{session_id}" , NameRegexp . String ( ) ) ,
2019-07-09 22:23:59 -07:00
rh . GetBlobUpload ) . Methods ( "GET" )
2020-01-09 16:31:34 -08:00
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/blobs/uploads/{session_id}" , NameRegexp . String ( ) ) ,
2019-07-09 22:23:59 -07:00
rh . PatchBlobUpload ) . Methods ( "PATCH" )
2020-01-09 16:31:34 -08:00
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/blobs/uploads/{session_id}" , NameRegexp . String ( ) ) ,
2019-07-09 22:23:59 -07:00
rh . UpdateBlobUpload ) . Methods ( "PUT" )
2020-01-09 16:31:34 -08:00
g . HandleFunc ( fmt . Sprintf ( "/{name:%s}/blobs/uploads/{session_id}" , NameRegexp . String ( ) ) ,
2019-07-09 22:23:59 -07:00
rh . DeleteBlobUpload ) . Methods ( "DELETE" )
g . HandleFunc ( "/_catalog" ,
rh . ListRepositories ) . Methods ( "GET" )
g . HandleFunc ( "/" ,
rh . CheckVersionSupport ) . Methods ( "GET" )
2019-06-20 16:36:40 -07:00
}
// swagger docs "/swagger/v2/index.html"
2019-07-09 22:23:59 -07:00
rh . c . Router . PathPrefix ( "/swagger/v2/" ) . Methods ( "GET" ) . Handler ( httpSwagger . WrapHandler )
2019-06-20 16:36:40 -07:00
}
// Method handlers
// CheckVersionSupport godoc
// @Summary Check API support
// @Description Check if this API version is supported
// @Router /v2/ [get]
// @Accept json
// @Produce json
// @Success 200 {string} string "ok"
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) CheckVersionSupport ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( DistAPIVersion , "registry/2.0" )
WriteData ( w , http . StatusOK , "application/json" , [ ] byte { } )
2019-06-20 16:36:40 -07:00
}
type ImageTags struct {
Name string ` json:"name" `
Tags [ ] string ` json:"tags" `
}
// ListTags godoc
// @Summary List image tags
// @Description List all image tags in a repository
// @Router /v2/{name}/tags/list [get]
// @Accept json
// @Produce json
// @Param name path string true "test"
2020-01-09 16:31:34 -08:00
// @Param n query integer true "limit entries for pagination"
// @Param last query string true "last tag value for pagination"
2019-06-20 16:36:40 -07:00
// @Success 200 {object} api.ImageTags
// @Failure 404 {string} string "not found"
2020-01-09 16:31:34 -08:00
// @Failure 400 {string} string "bad request"
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) ListTags ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2020-01-09 16:31:34 -08:00
paginate := false
n := - 1
var err error
nQuery , ok := r . URL . Query ( ) [ "n" ]
if ok {
if len ( nQuery ) != 1 {
w . WriteHeader ( http . StatusBadRequest )
return
}
var n1 int64
if n1 , err = strconv . ParseInt ( nQuery [ 0 ] , 10 , 0 ) ; err != nil {
w . WriteHeader ( http . StatusBadRequest )
return
}
n = int ( n1 )
paginate = true
}
last := ""
lastQuery , ok := r . URL . Query ( ) [ "last" ]
if ok {
if len ( lastQuery ) != 1 {
w . WriteHeader ( http . StatusBadRequest )
return
}
last = lastQuery [ 0 ]
}
2019-06-20 16:36:40 -07:00
tags , err := rh . c . ImageStore . GetImageTags ( name )
if err != nil {
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
return
}
2020-01-09 16:31:34 -08:00
if paginate && ( n < len ( tags ) ) {
sort . Strings ( tags )
pTags := ImageTags { Name : name }
if last == "" {
// first
pTags . Tags = tags [ : n ]
} else {
// next
i := - 1
tag := ""
found := false
for i , tag = range tags {
if tag == last {
found = true
break
}
}
if ! found {
w . WriteHeader ( http . StatusNotFound )
return
}
if n >= len ( tags ) - i {
pTags . Tags = tags [ i + 1 : ]
WriteJSON ( w , http . StatusOK , pTags )
return
}
pTags . Tags = tags [ i + 1 : i + 1 + n ]
}
last = pTags . Tags [ len ( pTags . Tags ) - 1 ]
w . Header ( ) . Set ( "Link" , fmt . Sprintf ( "/v2/%s/tags/list?n=%d&last=%s; rel=\"next\"" , name , n , last ) )
WriteJSON ( w , http . StatusOK , pTags )
return
}
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusOK , ImageTags { Name : name , Tags : tags } )
2019-06-20 16:36:40 -07:00
}
// CheckManifest godoc
// @Summary Check image manifest
// @Description Check an image's manifest given a reference or a digest
// @Router /v2/{name}/manifests/{reference} [head]
// @Accept json
// @Produce json
// @Param name path string true "repository name"
// @Param reference path string true "image reference or digest"
// @Success 200 {string} string "ok"
// @Header 200 {object} api.DistContentDigestKey
// @Failure 404 {string} string "not found"
// @Failure 500 {string} string "internal server error"
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) CheckManifest ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
reference , ok := vars [ "reference" ]
if ! ok || reference == "" {
WriteJSON ( w , http . StatusNotFound , NewError ( MANIFEST_INVALID , map [ string ] string { "reference" : reference } ) )
2019-06-20 16:36:40 -07:00
return
}
_ , digest , _ , err := rh . c . ImageStore . GetImageManifest ( name , reference )
if err != nil {
switch err {
case errors . ErrManifestNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( MANIFEST_UNKNOWN , map [ string ] string { "reference" : reference } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusInternalServerError , NewError ( MANIFEST_INVALID , map [ string ] string { "reference" : reference } ) )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
w . Header ( ) . Set ( DistContentDigestKey , digest )
w . Header ( ) . Set ( "Content-Length" , "0" )
w . WriteHeader ( http . StatusOK )
2019-06-20 16:36:40 -07:00
}
// NOTE: https://github.com/swaggo/swag/issues/387
type ImageManifest struct {
ispec . Manifest
}
// GetManifest godoc
// @Summary Get image manifest
// @Description Get an image's manifest given a reference or a digest
// @Accept json
// @Produce application/vnd.oci.image.manifest.v1+json
// @Param name path string true "repository name"
// @Param reference path string true "image reference or digest"
// @Success 200 {object} api.ImageManifest
// @Header 200 {object} api.DistContentDigestKey
// @Failure 404 {string} string "not found"
// @Failure 500 {string} string "internal server error"
// @Router /v2/{name}/manifests/{reference} [get]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) GetManifest ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
reference , ok := vars [ "reference" ]
if ! ok || reference == "" {
WriteJSON ( w , http . StatusNotFound , NewError ( MANIFEST_UNKNOWN , map [ string ] string { "reference" : reference } ) )
2019-06-20 16:36:40 -07:00
return
}
content , digest , mediaType , err := rh . c . ImageStore . GetImageManifest ( name , reference )
if err != nil {
switch err {
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrRepoBadVersion :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrManifestNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( MANIFEST_UNKNOWN , map [ string ] string { "reference" : reference } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
w . Header ( ) . Set ( DistContentDigestKey , digest )
2019-11-26 09:37:23 -08:00
WriteData ( w , http . StatusOK , mediaType , content )
2019-06-20 16:36:40 -07:00
}
// UpdateManifest godoc
// @Summary Update image manifest
// @Description Update an image's manifest given a reference or a digest
// @Accept json
// @Produce json
// @Param name path string true "repository name"
// @Param reference path string true "image reference or digest"
// @Header 201 {object} api.DistContentDigestKey
// @Success 201 {string} string "created"
// @Failure 400 {string} string "bad request"
// @Failure 404 {string} string "not found"
// @Failure 500 {string} string "internal server error"
// @Router /v2/{name}/manifests/{reference} [put]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) UpdateManifest ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
reference , ok := vars [ "reference" ]
if ! ok || reference == "" {
WriteJSON ( w , http . StatusNotFound , NewError ( MANIFEST_INVALID , map [ string ] string { "reference" : reference } ) )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
mediaType := r . Header . Get ( "Content-Type" )
2019-06-20 16:36:40 -07:00
if mediaType != ispec . MediaTypeImageManifest {
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusUnsupportedMediaType )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
body , err := ioutil . ReadAll ( r . Body )
2019-06-20 16:36:40 -07:00
if err != nil {
2020-01-06 16:21:46 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2020-01-06 16:21:46 -08:00
2019-06-20 16:36:40 -07:00
return
}
digest , err := rh . c . ImageStore . PutImageManifest ( name , reference , mediaType , body )
if err != nil {
switch err {
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrManifestNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( MANIFEST_UNKNOWN , map [ string ] string { "reference" : reference } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrBadManifest :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusBadRequest , NewError ( MANIFEST_INVALID , map [ string ] string { "reference" : reference } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrBlobNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusBadRequest , NewError ( BLOB_UNKNOWN , map [ string ] string { "blob" : digest } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
w . Header ( ) . Set ( "Location" , fmt . Sprintf ( "/v2/%s/manifests/%s" , name , digest ) )
w . Header ( ) . Set ( DistContentDigestKey , digest )
w . WriteHeader ( http . StatusCreated )
2019-06-20 16:36:40 -07:00
}
// DeleteManifest godoc
// @Summary Delete image manifest
// @Description Delete an image's manifest given a reference or a digest
// @Accept json
// @Produce json
// @Param name path string true "repository name"
// @Param reference path string true "image reference or digest"
// @Success 200 {string} string "ok"
// @Router /v2/{name}/manifests/{reference} [delete]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) DeleteManifest ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
reference , ok := vars [ "reference" ]
if ! ok || reference == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
err := rh . c . ImageStore . DeleteImageManifest ( name , reference )
if err != nil {
switch err {
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrManifestNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( MANIFEST_UNKNOWN , map [ string ] string { "reference" : reference } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
2020-01-09 16:31:34 -08:00
w . WriteHeader ( http . StatusAccepted )
2019-06-20 16:36:40 -07:00
}
// CheckBlob godoc
// @Summary Check image blob/layer
// @Description Check an image's blob/layer given a digest
// @Accept json
// @Produce json
// @Param name path string true "repository name"
// @Param digest path string true "blob/layer digest"
// @Success 200 {object} api.ImageManifest
// @Header 200 {object} api.DistContentDigestKey
// @Router /v2/{name}/blobs/{digest} [head]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) CheckBlob ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
digest , ok := vars [ "digest" ]
if ! ok || digest == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
mediaType := r . Header . Get ( "Accept" )
2019-06-20 16:36:40 -07:00
ok , blen , err := rh . c . ImageStore . CheckBlob ( name , digest , mediaType )
if err != nil {
switch err {
case errors . ErrBadBlobDigest :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusBadRequest , NewError ( DIGEST_INVALID , map [ string ] string { "digest" : digest } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrBlobNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( BLOB_UNKNOWN , map [ string ] string { "digest" : digest } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
if ! ok {
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( BLOB_UNKNOWN , map [ string ] string { "digest" : digest } ) )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
w . Header ( ) . Set ( "Content-Length" , fmt . Sprintf ( "%d" , blen ) )
w . Header ( ) . Set ( DistContentDigestKey , digest )
w . WriteHeader ( http . StatusOK )
2019-06-20 16:36:40 -07:00
}
// GetBlob godoc
// @Summary Get image blob/layer
// @Description Get an image's blob/layer given a digest
// @Accept json
// @Produce application/vnd.oci.image.layer.v1.tar+gzip
// @Param name path string true "repository name"
// @Param digest path string true "blob/layer digest"
// @Header 200 {object} api.DistContentDigestKey
// @Success 200 {object} api.ImageManifest
// @Router /v2/{name}/blobs/{digest} [get]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) GetBlob ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
digest , ok := vars [ "digest" ]
if ! ok || digest == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
mediaType := r . Header . Get ( "Accept" )
2019-06-20 16:36:40 -07:00
br , blen , err := rh . c . ImageStore . GetBlob ( name , digest , mediaType )
if err != nil {
switch err {
case errors . ErrBadBlobDigest :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusBadRequest , NewError ( DIGEST_INVALID , map [ string ] string { "digest" : digest } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrBlobNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( BLOB_UNKNOWN , map [ string ] string { "digest" : digest } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
w . Header ( ) . Set ( "Content-Length" , fmt . Sprintf ( "%d" , blen ) )
w . Header ( ) . Set ( DistContentDigestKey , digest )
2019-06-20 16:36:40 -07:00
// return the blob data
2020-01-04 18:20:06 -08:00
WriteDataFromReader ( w , http . StatusOK , blen , mediaType , br , rh . c . Log )
2019-06-20 16:36:40 -07:00
}
// DeleteBlob godoc
// @Summary Delete image blob/layer
// @Description Delete an image's blob/layer given a digest
// @Accept json
// @Produce json
// @Param name path string true "repository name"
// @Param digest path string true "blob/layer digest"
// @Success 202 {string} string "accepted"
// @Router /v2/{name}/blobs/{digest} [delete]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) DeleteBlob ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
digest , ok := vars [ "digest" ]
if ! ok || digest == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
err := rh . c . ImageStore . DeleteBlob ( name , digest )
if err != nil {
switch err {
case errors . ErrBadBlobDigest :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusBadRequest , NewError ( DIGEST_INVALID , map [ string ] string { "digest" : digest } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrBlobNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( BLOB_UNKNOWN , map [ string ] string { "digest" : digest } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusAccepted )
2019-06-20 16:36:40 -07:00
}
// CreateBlobUpload godoc
// @Summary Create image blob/layer upload
// @Description Create a new image blob/layer upload
// @Accept json
// @Produce json
// @Param name path string true "repository name"
// @Success 202 {string} string "accepted"
2020-01-09 16:31:34 -08:00
// @Header 202 {string} Location "/v2/{name}/blobs/uploads/{session_id}"
2019-06-20 16:36:40 -07:00
// @Header 202 {string} Range "bytes=0-0"
// @Failure 404 {string} string "not found"
// @Failure 500 {string} string "internal server error"
// @Router /v2/{name}/blobs/uploads [post]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) CreateBlobUpload ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-12-20 10:37:41 -08:00
// blob mounts not allowed since we don't have access control yet, and this
// may be a uncommon use case, but remain compliant
if _ , ok := r . URL . Query ( ) [ "mount" ] ; ok {
w . WriteHeader ( http . StatusMethodNotAllowed )
return
}
if _ , ok := r . URL . Query ( ) [ "from" ] ; ok {
w . WriteHeader ( http . StatusMethodNotAllowed )
return
}
2019-06-20 16:36:40 -07:00
u , err := rh . c . ImageStore . NewBlobUpload ( name )
if err != nil {
switch err {
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
w . Header ( ) . Set ( "Location" , path . Join ( r . URL . String ( ) , u ) )
w . Header ( ) . Set ( "Range" , "bytes=0-0" )
w . WriteHeader ( http . StatusAccepted )
2019-06-20 16:36:40 -07:00
}
// GetBlobUpload godoc
// @Summary Get image blob/layer upload
2020-01-09 16:31:34 -08:00
// @Description Get an image's blob/layer upload given a session_id
2019-06-20 16:36:40 -07:00
// @Accept json
// @Produce json
// @Param name path string true "repository name"
2020-01-09 16:31:34 -08:00
// @Param session_id path string true "upload session_id"
2019-06-20 16:36:40 -07:00
// @Success 204 {string} string "no content"
2020-01-09 16:31:34 -08:00
// @Header 202 {string} Location "/v2/{name}/blobs/uploads/{session_id}"
2019-06-20 16:36:40 -07:00
// @Header 202 {string} Range "bytes=0-128"
// @Failure 404 {string} string "not found"
// @Failure 500 {string} string "internal server error"
2020-01-09 16:31:34 -08:00
// @Router /v2/{name}/blobs/uploads/{session_id} [get]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) GetBlobUpload ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2020-01-09 16:31:34 -08:00
sessionID , ok := vars [ "session_id" ]
if ! ok || sessionID == "" {
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2020-01-09 16:31:34 -08:00
size , err := rh . c . ImageStore . GetBlobUpload ( name , sessionID )
2019-06-20 16:36:40 -07:00
if err != nil {
switch err {
case errors . ErrBadUploadRange :
2020-01-09 16:31:34 -08:00
WriteJSON ( w , http . StatusBadRequest , NewError ( BLOB_UPLOAD_INVALID , map [ string ] string { "session_id" : sessionID } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrBadBlobDigest :
2020-01-09 16:31:34 -08:00
WriteJSON ( w , http . StatusBadRequest , NewError ( BLOB_UPLOAD_INVALID , map [ string ] string { "session_id" : sessionID } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrUploadNotFound :
2020-01-09 16:31:34 -08:00
WriteJSON ( w , http . StatusNotFound , NewError ( BLOB_UPLOAD_UNKNOWN , map [ string ] string { "session_id" : sessionID } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
2020-01-09 16:31:34 -08:00
w . Header ( ) . Set ( "Location" , path . Join ( r . URL . String ( ) , sessionID ) )
w . Header ( ) . Set ( "Range" , fmt . Sprintf ( "bytes=0-%d" , size - 1 ) )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusNoContent )
2019-06-20 16:36:40 -07:00
}
// PatchBlobUpload godoc
// @Summary Resume image blob/layer upload
2020-01-09 16:31:34 -08:00
// @Description Resume an image's blob/layer upload given an session_id
2019-06-20 16:36:40 -07:00
// @Accept json
// @Produce json
// @Param name path string true "repository name"
2020-01-09 16:31:34 -08:00
// @Param session_id path string true "upload session_id"
2019-06-20 16:36:40 -07:00
// @Success 202 {string} string "accepted"
2020-01-09 16:31:34 -08:00
// @Header 202 {string} Location "/v2/{name}/blobs/uploads/{session_id}"
2019-06-20 16:36:40 -07:00
// @Header 202 {string} Range "bytes=0-128"
// @Header 200 {object} api.BlobUploadUUID
// @Failure 400 {string} string "bad request"
// @Failure 404 {string} string "not found"
// @Failure 416 {string} string "range not satisfiable"
// @Failure 500 {string} string "internal server error"
2020-01-09 16:31:34 -08:00
// @Router /v2/{name}/blobs/uploads/{session_id} [patch]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) PatchBlobUpload ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-12-13 00:53:18 -05:00
2020-01-09 16:31:34 -08:00
sessionID , ok := vars [ "session_id" ]
if ! ok || sessionID == "" {
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2020-01-09 16:31:34 -08:00
if contentType := r . Header . Get ( "Content-Type" ) ; contentType != BinaryMediaType {
rh . c . Log . Warn ( ) . Str ( "actual" , contentType ) . Str ( "expected" , BinaryMediaType ) . Msg ( "invalid media type" )
w . WriteHeader ( http . StatusUnsupportedMediaType )
return
}
2019-06-20 16:36:40 -07:00
var err error
2019-12-13 00:53:18 -05:00
2020-01-09 16:31:34 -08:00
var clen int64
2019-12-13 00:53:18 -05:00
2020-01-09 16:31:34 -08:00
if r . Header . Get ( "Content-Length" ) == "" || r . Header . Get ( "Content-Range" ) == "" {
// streamed blob upload
clen , err = rh . c . ImageStore . PutBlobChunkStreamed ( name , sessionID , r . Body )
} else {
// chunked blob upload
2019-12-13 00:53:18 -05:00
2020-01-09 16:31:34 -08:00
var contentLength int64
2019-06-20 16:36:40 -07:00
2020-01-09 16:31:34 -08:00
if contentLength , err = strconv . ParseInt ( r . Header . Get ( "Content-Length" ) , 10 , 64 ) ; err != nil {
rh . c . Log . Warn ( ) . Str ( "actual" , r . Header . Get ( "Content-Length" ) ) . Msg ( "invalid content length" )
w . WriteHeader ( http . StatusBadRequest )
2019-12-13 00:53:18 -05:00
2020-01-09 16:31:34 -08:00
return
}
2019-06-20 16:36:40 -07:00
2020-01-09 16:31:34 -08:00
contentRange := r . Header . Get ( "Content-Range" )
if contentRange == "" {
rh . c . Log . Warn ( ) . Str ( "actual" , r . Header . Get ( "Content-Range" ) ) . Msg ( "invalid content range" )
w . WriteHeader ( http . StatusRequestedRangeNotSatisfiable )
2019-06-20 16:36:40 -07:00
2020-01-09 16:31:34 -08:00
return
}
2019-12-13 00:53:18 -05:00
2020-01-09 16:31:34 -08:00
var from , to int64
if from , to , err = getContentRange ( r ) ; err != nil || ( to - from ) + 1 != contentLength {
w . WriteHeader ( http . StatusRequestedRangeNotSatisfiable )
return
}
clen , err = rh . c . ImageStore . PutBlobChunk ( name , sessionID , from , to , r . Body )
2019-06-20 16:36:40 -07:00
}
if err != nil {
switch err {
case errors . ErrBadUploadRange :
2020-01-09 16:31:34 -08:00
WriteJSON ( w , http . StatusBadRequest , NewError ( BLOB_UPLOAD_INVALID , map [ string ] string { "session_id" : sessionID } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrUploadNotFound :
2020-01-09 16:31:34 -08:00
WriteJSON ( w , http . StatusNotFound , NewError ( BLOB_UPLOAD_UNKNOWN , map [ string ] string { "session_id" : sessionID } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
2020-01-09 16:31:34 -08:00
w . Header ( ) . Set ( "Location" , r . URL . String ( ) )
w . Header ( ) . Set ( "Range" , fmt . Sprintf ( "bytes=0-%d" , clen - 1 ) )
2019-07-09 22:23:59 -07:00
w . Header ( ) . Set ( "Content-Length" , "0" )
2020-01-09 16:31:34 -08:00
w . Header ( ) . Set ( BlobUploadUUID , sessionID )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusAccepted )
2019-06-20 16:36:40 -07:00
}
// UpdateBlobUpload godoc
// @Summary Update image blob/layer upload
// @Description Update and finish an image's blob/layer upload given a digest
// @Accept json
// @Produce json
// @Param name path string true "repository name"
2020-01-09 16:31:34 -08:00
// @Param session_id path string true "upload session_id"
2019-06-20 16:36:40 -07:00
// @Param digest query string true "blob/layer digest"
// @Success 201 {string} string "created"
2020-01-09 16:31:34 -08:00
// @Header 202 {string} Location "/v2/{name}/blobs/uploads/{digest}"
2019-06-20 16:36:40 -07:00
// @Header 200 {object} api.DistContentDigestKey
// @Failure 404 {string} string "not found"
// @Failure 500 {string} string "internal server error"
2020-01-09 16:31:34 -08:00
// @Router /v2/{name}/blobs/uploads/{session_id} [put]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) UpdateBlobUpload ( w http . ResponseWriter , r * http . Request ) {
2020-01-09 16:31:34 -08:00
rh . c . Log . Info ( ) . Interface ( "headers" , r . Header ) . Msg ( "HEADERS" )
2019-07-09 22:23:59 -07:00
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2020-01-09 16:31:34 -08:00
sessionID , ok := vars [ "session_id" ]
if ! ok || sessionID == "" {
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
digests , ok := r . URL . Query ( ) [ "digest" ]
if ! ok || len ( digests ) != 1 {
w . WriteHeader ( http . StatusBadRequest )
2019-06-20 16:36:40 -07:00
return
}
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
digest := digests [ 0 ]
2019-06-20 16:36:40 -07:00
2020-01-09 16:31:34 -08:00
rh . c . Log . Info ( ) . Int64 ( "r.ContentLength" , r . ContentLength ) . Msg ( "DEBUG" )
2019-06-20 16:36:40 -07:00
contentPresent := true
2019-07-09 22:23:59 -07:00
contentLen , err := strconv . ParseInt ( r . Header . Get ( "Content-Length" ) , 10 , 64 )
2019-12-13 00:53:18 -05:00
2020-01-09 16:31:34 -08:00
if err != nil {
2019-06-20 16:36:40 -07:00
contentPresent = false
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
contentRangePresent := true
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if r . Header . Get ( "Content-Range" ) == "" {
2019-06-20 16:36:40 -07:00
contentRangePresent = false
}
// we expect at least one of "Content-Length" or "Content-Range" to be
// present
if ! contentPresent && ! contentRangePresent {
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusBadRequest )
2019-06-20 16:36:40 -07:00
return
}
var from , to int64
if contentPresent {
2019-07-09 22:23:59 -07:00
contentRange := r . Header . Get ( "Content-Range" )
2019-06-20 16:36:40 -07:00
if contentRange == "" { // monolithic upload
from = 0
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
if contentLen == 0 {
2020-01-09 16:31:34 -08:00
goto finish // FIXME:
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
to = contentLen
2019-07-09 22:23:59 -07:00
} else if from , to , err = getContentRange ( r ) ; err != nil { // finish chunked upload
w . WriteHeader ( http . StatusRequestedRangeNotSatisfiable )
2019-06-20 16:36:40 -07:00
return
}
2020-01-09 16:31:34 -08:00
if r . Header . Get ( "Content-Type" ) != BinaryMediaType {
w . WriteHeader ( http . StatusUnsupportedMediaType )
return
}
_ , err = rh . c . ImageStore . PutBlobChunk ( name , sessionID , from , to , r . Body )
2019-06-20 16:36:40 -07:00
if err != nil {
switch err {
case errors . ErrBadUploadRange :
2020-01-09 16:31:34 -08:00
WriteJSON ( w , http . StatusBadRequest , NewError ( BLOB_UPLOAD_INVALID , map [ string ] string { "session_id" : sessionID } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrUploadNotFound :
2020-01-09 16:31:34 -08:00
WriteJSON ( w , http . StatusNotFound , NewError ( BLOB_UPLOAD_UNKNOWN , map [ string ] string { "session_id" : sessionID } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
}
2020-01-09 16:31:34 -08:00
finish :
if r . Header . Get ( "Content-Type" ) != BinaryMediaType {
w . WriteHeader ( http . StatusUnsupportedMediaType )
return
}
2019-06-20 16:36:40 -07:00
// blob chunks already transferred, just finish
2020-01-09 16:31:34 -08:00
if err := rh . c . ImageStore . FinishBlobUpload ( name , sessionID , r . Body , digest ) ; err != nil {
2019-06-20 16:36:40 -07:00
switch err {
case errors . ErrBadBlobDigest :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusBadRequest , NewError ( DIGEST_INVALID , map [ string ] string { "digest" : digest } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrBadUploadRange :
2020-01-09 16:31:34 -08:00
WriteJSON ( w , http . StatusBadRequest , NewError ( BLOB_UPLOAD_INVALID , map [ string ] string { "session_id" : sessionID } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrUploadNotFound :
2020-01-09 16:31:34 -08:00
WriteJSON ( w , http . StatusNotFound , NewError ( BLOB_UPLOAD_UNKNOWN , map [ string ] string { "session_id" : sessionID } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
2019-07-09 22:23:59 -07:00
w . Header ( ) . Set ( "Location" , fmt . Sprintf ( "/v2/%s/blobs/%s" , name , digest ) )
w . Header ( ) . Set ( "Content-Length" , "0" )
w . Header ( ) . Set ( DistContentDigestKey , digest )
w . WriteHeader ( http . StatusCreated )
2019-06-20 16:36:40 -07:00
}
// DeleteBlobUpload godoc
// @Summary Delete image blob/layer
// @Description Delete an image's blob/layer given a digest
// @Accept json
// @Produce json
// @Param name path string true "repository name"
2020-01-09 16:31:34 -08:00
// @Param session_id path string true "upload session_id"
2019-06-20 16:36:40 -07:00
// @Success 200 {string} string "ok"
// @Failure 404 {string} string "not found"
// @Failure 500 {string} string "internal server error"
2020-01-09 16:31:34 -08:00
// @Router /v2/{name}/blobs/uploads/{session_id} [delete]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) DeleteBlobUpload ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
name , ok := vars [ "name" ]
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if ! ok || name == "" {
w . WriteHeader ( http . StatusNotFound )
2019-06-20 16:36:40 -07:00
return
}
2020-01-09 16:31:34 -08:00
sessionID , ok := vars [ "session_id" ]
if ! ok || sessionID == "" {
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusNotFound )
return
}
2019-06-20 16:36:40 -07:00
2020-01-09 16:31:34 -08:00
if err := rh . c . ImageStore . DeleteBlobUpload ( name , sessionID ) ; err != nil {
2019-06-20 16:36:40 -07:00
switch err {
case errors . ErrRepoNotFound :
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusNotFound , NewError ( NAME_UNKNOWN , map [ string ] string { "name" : name } ) )
2019-06-20 16:36:40 -07:00
case errors . ErrUploadNotFound :
2020-01-09 16:31:34 -08:00
WriteJSON ( w , http . StatusNotFound , NewError ( BLOB_UPLOAD_UNKNOWN , map [ string ] string { "session_id" : sessionID } ) )
2019-06-20 16:36:40 -07:00
default :
2019-11-25 14:33:58 -08:00
rh . c . Log . Error ( ) . Err ( err ) . Msg ( "unexpected error" )
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return
}
2019-12-23 22:32:52 -08:00
w . WriteHeader ( http . StatusNoContent )
2019-06-20 16:36:40 -07:00
}
type RepositoryList struct {
Repositories [ ] string ` json:"repositories" `
}
// ListRepositories godoc
// @Summary List image repositories
// @Description List all image repositories
// @Accept json
// @Produce json
// @Success 200 {object} api.RepositoryList
// @Failure 500 {string} string "internal server error"
// @Router /v2/_catalog [get]
2019-07-09 22:23:59 -07:00
func ( rh * RouteHandler ) ListRepositories ( w http . ResponseWriter , r * http . Request ) {
2019-06-20 16:36:40 -07:00
repos , err := rh . c . ImageStore . GetRepositories ( )
if err != nil {
2019-07-09 22:23:59 -07:00
w . WriteHeader ( http . StatusInternalServerError )
2019-06-20 16:36:40 -07:00
return
}
is := RepositoryList { Repositories : repos }
2019-07-09 22:23:59 -07:00
WriteJSON ( w , http . StatusOK , is )
2019-06-20 16:36:40 -07:00
}
// helper routines
2019-07-09 22:23:59 -07:00
func getContentRange ( r * http . Request ) ( int64 /* from */ , int64 /* to */ , error ) {
contentRange := r . Header . Get ( "Content-Range" )
2019-06-20 16:36:40 -07:00
tokens := strings . Split ( contentRange , "-" )
from , err := strconv . ParseInt ( tokens [ 0 ] , 10 , 64 )
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
if err != nil {
return - 1 , - 1 , errors . ErrBadUploadRange
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
to , err := strconv . ParseInt ( tokens [ 1 ] , 10 , 64 )
if err != nil {
return - 1 , - 1 , errors . ErrBadUploadRange
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
if from > to {
return - 1 , - 1 , errors . ErrBadUploadRange
}
2019-12-13 00:53:18 -05:00
2019-06-20 16:36:40 -07:00
return from , to , nil
}
2019-07-09 22:23:59 -07:00
func WriteJSON ( w http . ResponseWriter , status int , data interface { } ) {
var json = jsoniter . ConfigCompatibleWithStandardLibrary
body , err := json . Marshal ( data )
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
if err != nil {
2020-01-06 16:21:46 -08:00
panic ( err )
2019-07-09 22:23:59 -07:00
}
2019-12-13 00:53:18 -05:00
2019-10-09 11:50:10 -07:00
WriteData ( w , status , DefaultMediaType , body )
2019-07-09 22:23:59 -07:00
}
func WriteData ( w http . ResponseWriter , status int , mediaType string , data [ ] byte ) {
w . Header ( ) . Set ( "Content-Type" , mediaType )
w . WriteHeader ( status )
_ , _ = w . Write ( data )
}
2020-01-04 18:20:06 -08:00
func WriteDataFromReader ( w http . ResponseWriter , status int , length int64 , mediaType string ,
reader io . Reader , logger log . Logger ) {
2019-07-09 22:23:59 -07:00
w . Header ( ) . Set ( "Content-Type" , mediaType )
w . Header ( ) . Set ( "Content-Length" , strconv . FormatInt ( length , 10 ) )
2020-01-04 18:20:06 -08:00
w . WriteHeader ( status )
2019-07-09 22:23:59 -07:00
const maxSize = 10 * 1024 * 1024
2019-12-13 00:53:18 -05:00
2019-07-09 22:23:59 -07:00
for {
2020-01-04 18:20:06 -08:00
_ , err := io . CopyN ( w , reader , maxSize )
if err == io . EOF {
2019-07-09 22:23:59 -07:00
break
2020-01-04 18:20:06 -08:00
} else if err != nil {
// other kinds of intermittent errors can occur, e.g, io.ErrShortWrite
2020-01-06 16:21:46 -08:00
logger . Error ( ) . Err ( err ) . Msg ( "copying data into http response" )
return
2019-07-09 22:23:59 -07:00
}
}
}