github.com/artpar/rclone@v1.67.3/cmd/serve/docker/api.go (about)

     1  package docker
     2  
     3  import (
     4  	"encoding/json"
     5  	"net/http"
     6  
     7  	"github.com/artpar/rclone/fs"
     8  	"github.com/go-chi/chi/v5"
     9  )
    10  
    11  const (
    12  	contentType  = "application/vnd.docker.plugins.v1.1+json"
    13  	activatePath = "/Plugin.Activate"
    14  	createPath   = "/VolumeDriver.Create"
    15  	getPath      = "/VolumeDriver.Get"
    16  	listPath     = "/VolumeDriver.List"
    17  	removePath   = "/VolumeDriver.Remove"
    18  	pathPath     = "/VolumeDriver.Path"
    19  	mountPath    = "/VolumeDriver.Mount"
    20  	unmountPath  = "/VolumeDriver.Unmount"
    21  	capsPath     = "/VolumeDriver.Capabilities"
    22  )
    23  
    24  // CreateRequest is the structure that docker's requests are deserialized to.
    25  type CreateRequest struct {
    26  	Name    string
    27  	Options map[string]string `json:"Opts,omitempty"`
    28  }
    29  
    30  // RemoveRequest structure for a volume remove request
    31  type RemoveRequest struct {
    32  	Name string
    33  }
    34  
    35  // MountRequest structure for a volume mount request
    36  type MountRequest struct {
    37  	Name string
    38  	ID   string
    39  }
    40  
    41  // MountResponse structure for a volume mount response
    42  type MountResponse struct {
    43  	Mountpoint string
    44  }
    45  
    46  // UnmountRequest structure for a volume unmount request
    47  type UnmountRequest struct {
    48  	Name string
    49  	ID   string
    50  }
    51  
    52  // PathRequest structure for a volume path request
    53  type PathRequest struct {
    54  	Name string
    55  }
    56  
    57  // PathResponse structure for a volume path response
    58  type PathResponse struct {
    59  	Mountpoint string
    60  }
    61  
    62  // GetRequest structure for a volume get request
    63  type GetRequest struct {
    64  	Name string
    65  }
    66  
    67  // GetResponse structure for a volume get response
    68  type GetResponse struct {
    69  	Volume *VolInfo
    70  }
    71  
    72  // ListResponse structure for a volume list response
    73  type ListResponse struct {
    74  	Volumes []*VolInfo
    75  }
    76  
    77  // CapabilitiesResponse structure for a volume capability response
    78  type CapabilitiesResponse struct {
    79  	Capabilities Capability
    80  }
    81  
    82  // Capability represents the list of capabilities a volume driver can return
    83  type Capability struct {
    84  	Scope string
    85  }
    86  
    87  // ErrorResponse is a formatted error message that docker can understand
    88  type ErrorResponse struct {
    89  	Err string
    90  }
    91  
    92  func newRouter(drv *Driver) http.Handler {
    93  	r := chi.NewRouter()
    94  	r.Post(activatePath, func(w http.ResponseWriter, r *http.Request) {
    95  		res := map[string]interface{}{
    96  			"Implements": []string{"VolumeDriver"},
    97  		}
    98  		encodeResponse(w, res, nil, activatePath)
    99  	})
   100  	r.Post(createPath, func(w http.ResponseWriter, r *http.Request) {
   101  		var req CreateRequest
   102  		if decodeRequest(w, r, &req) {
   103  			err := drv.Create(&req)
   104  			encodeResponse(w, nil, err, createPath)
   105  		}
   106  	})
   107  	r.Post(removePath, func(w http.ResponseWriter, r *http.Request) {
   108  		var req RemoveRequest
   109  		if decodeRequest(w, r, &req) {
   110  			err := drv.Remove(&req)
   111  			encodeResponse(w, nil, err, removePath)
   112  		}
   113  	})
   114  	r.Post(mountPath, func(w http.ResponseWriter, r *http.Request) {
   115  		var req MountRequest
   116  		if decodeRequest(w, r, &req) {
   117  			res, err := drv.Mount(&req)
   118  			encodeResponse(w, res, err, mountPath)
   119  		}
   120  	})
   121  	r.Post(pathPath, func(w http.ResponseWriter, r *http.Request) {
   122  		var req PathRequest
   123  		if decodeRequest(w, r, &req) {
   124  			res, err := drv.Path(&req)
   125  			encodeResponse(w, res, err, pathPath)
   126  		}
   127  	})
   128  	r.Post(getPath, func(w http.ResponseWriter, r *http.Request) {
   129  		var req GetRequest
   130  		if decodeRequest(w, r, &req) {
   131  			res, err := drv.Get(&req)
   132  			encodeResponse(w, res, err, getPath)
   133  		}
   134  	})
   135  	r.Post(unmountPath, func(w http.ResponseWriter, r *http.Request) {
   136  		var req UnmountRequest
   137  		if decodeRequest(w, r, &req) {
   138  			err := drv.Unmount(&req)
   139  			encodeResponse(w, nil, err, unmountPath)
   140  		}
   141  	})
   142  	r.Post(listPath, func(w http.ResponseWriter, r *http.Request) {
   143  		res, err := drv.List()
   144  		encodeResponse(w, res, err, listPath)
   145  	})
   146  	r.Post(capsPath, func(w http.ResponseWriter, r *http.Request) {
   147  		res := &CapabilitiesResponse{
   148  			Capabilities: Capability{Scope: pluginScope},
   149  		}
   150  		encodeResponse(w, res, nil, capsPath)
   151  	})
   152  	return r
   153  }
   154  
   155  func decodeRequest(w http.ResponseWriter, r *http.Request, req interface{}) bool {
   156  	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
   157  		http.Error(w, err.Error(), http.StatusBadRequest)
   158  		return false
   159  	}
   160  	return true
   161  }
   162  
   163  func encodeResponse(w http.ResponseWriter, res interface{}, err error, path string) {
   164  	w.Header().Set("Content-Type", contentType)
   165  	if err != nil {
   166  		fs.Debugf(path, "Request returned error: %v", err)
   167  		w.WriteHeader(http.StatusInternalServerError)
   168  		res = &ErrorResponse{Err: err.Error()}
   169  	} else if res == nil {
   170  		res = struct{}{}
   171  	}
   172  	if err = json.NewEncoder(w).Encode(res); err != nil {
   173  		fs.Debugf(path, "Response encoding failed: %v", err)
   174  	}
   175  }