plugin_routes.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. package plugin // import "github.com/docker/docker/api/server/router/plugin"
  2. import (
  3. "context"
  4. "net/http"
  5. "strconv"
  6. "strings"
  7. "github.com/distribution/reference"
  8. "github.com/docker/docker/api/server/httputils"
  9. "github.com/docker/docker/api/types"
  10. "github.com/docker/docker/api/types/backend"
  11. "github.com/docker/docker/api/types/filters"
  12. "github.com/docker/docker/api/types/registry"
  13. "github.com/docker/docker/pkg/ioutils"
  14. "github.com/docker/docker/pkg/streamformatter"
  15. "github.com/pkg/errors"
  16. )
  17. func parseHeaders(headers http.Header) (map[string][]string, *registry.AuthConfig) {
  18. metaHeaders := map[string][]string{}
  19. for k, v := range headers {
  20. if strings.HasPrefix(k, "X-Meta-") {
  21. metaHeaders[k] = v
  22. }
  23. }
  24. // Ignore invalid AuthConfig to increase compatibility with the existing API.
  25. authConfig, _ := registry.DecodeAuthConfig(headers.Get(registry.AuthHeader))
  26. return metaHeaders, authConfig
  27. }
  28. // parseRemoteRef parses the remote reference into a reference.Named
  29. // returning the tag associated with the reference. In the case the
  30. // given reference string includes both digest and tag, the returned
  31. // reference will have the digest without the tag, but the tag will
  32. // be returned.
  33. func parseRemoteRef(remote string) (reference.Named, string, error) {
  34. // Parse remote reference, supporting remotes with name and tag
  35. remoteRef, err := reference.ParseNormalizedNamed(remote)
  36. if err != nil {
  37. return nil, "", err
  38. }
  39. type canonicalWithTag interface {
  40. reference.Canonical
  41. Tag() string
  42. }
  43. if canonical, ok := remoteRef.(canonicalWithTag); ok {
  44. remoteRef, err = reference.WithDigest(reference.TrimNamed(remoteRef), canonical.Digest())
  45. if err != nil {
  46. return nil, "", err
  47. }
  48. return remoteRef, canonical.Tag(), nil
  49. }
  50. remoteRef = reference.TagNameOnly(remoteRef)
  51. return remoteRef, "", nil
  52. }
  53. func (pr *pluginRouter) getPrivileges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  54. if err := httputils.ParseForm(r); err != nil {
  55. return err
  56. }
  57. metaHeaders, authConfig := parseHeaders(r.Header)
  58. ref, _, err := parseRemoteRef(r.FormValue("remote"))
  59. if err != nil {
  60. return err
  61. }
  62. privileges, err := pr.backend.Privileges(ctx, ref, metaHeaders, authConfig)
  63. if err != nil {
  64. return err
  65. }
  66. return httputils.WriteJSON(w, http.StatusOK, privileges)
  67. }
  68. func (pr *pluginRouter) upgradePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  69. if err := httputils.ParseForm(r); err != nil {
  70. return errors.Wrap(err, "failed to parse form")
  71. }
  72. var privileges types.PluginPrivileges
  73. if err := httputils.ReadJSON(r, &privileges); err != nil {
  74. return err
  75. }
  76. metaHeaders, authConfig := parseHeaders(r.Header)
  77. ref, tag, err := parseRemoteRef(r.FormValue("remote"))
  78. if err != nil {
  79. return err
  80. }
  81. name, err := getName(ref, tag, vars["name"])
  82. if err != nil {
  83. return err
  84. }
  85. w.Header().Set("Docker-Plugin-Name", name)
  86. w.Header().Set("Content-Type", "application/json")
  87. output := ioutils.NewWriteFlusher(w)
  88. if err := pr.backend.Upgrade(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil {
  89. if !output.Flushed() {
  90. return err
  91. }
  92. _, _ = output.Write(streamformatter.FormatError(err))
  93. }
  94. return nil
  95. }
  96. func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  97. if err := httputils.ParseForm(r); err != nil {
  98. return errors.Wrap(err, "failed to parse form")
  99. }
  100. var privileges types.PluginPrivileges
  101. if err := httputils.ReadJSON(r, &privileges); err != nil {
  102. return err
  103. }
  104. metaHeaders, authConfig := parseHeaders(r.Header)
  105. ref, tag, err := parseRemoteRef(r.FormValue("remote"))
  106. if err != nil {
  107. return err
  108. }
  109. name, err := getName(ref, tag, r.FormValue("name"))
  110. if err != nil {
  111. return err
  112. }
  113. w.Header().Set("Docker-Plugin-Name", name)
  114. w.Header().Set("Content-Type", "application/json")
  115. output := ioutils.NewWriteFlusher(w)
  116. if err := pr.backend.Pull(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil {
  117. if !output.Flushed() {
  118. return err
  119. }
  120. _, _ = output.Write(streamformatter.FormatError(err))
  121. }
  122. return nil
  123. }
  124. func getName(ref reference.Named, tag, name string) (string, error) {
  125. if name == "" {
  126. if _, ok := ref.(reference.Canonical); ok {
  127. trimmed := reference.TrimNamed(ref)
  128. if tag != "" {
  129. nt, err := reference.WithTag(trimmed, tag)
  130. if err != nil {
  131. return "", err
  132. }
  133. name = reference.FamiliarString(nt)
  134. } else {
  135. name = reference.FamiliarString(reference.TagNameOnly(trimmed))
  136. }
  137. } else {
  138. name = reference.FamiliarString(ref)
  139. }
  140. } else {
  141. localRef, err := reference.ParseNormalizedNamed(name)
  142. if err != nil {
  143. return "", err
  144. }
  145. if _, ok := localRef.(reference.Canonical); ok {
  146. return "", errors.New("cannot use digest in plugin tag")
  147. }
  148. if reference.IsNameOnly(localRef) {
  149. // TODO: log change in name to out stream
  150. name = reference.FamiliarString(reference.TagNameOnly(localRef))
  151. }
  152. }
  153. return name, nil
  154. }
  155. func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  156. if err := httputils.ParseForm(r); err != nil {
  157. return err
  158. }
  159. options := &types.PluginCreateOptions{
  160. RepoName: r.FormValue("name"),
  161. }
  162. if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil {
  163. return err
  164. }
  165. // TODO: send progress bar
  166. w.WriteHeader(http.StatusNoContent)
  167. return nil
  168. }
  169. func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  170. if err := httputils.ParseForm(r); err != nil {
  171. return err
  172. }
  173. name := vars["name"]
  174. timeout, err := strconv.Atoi(r.Form.Get("timeout"))
  175. if err != nil {
  176. return err
  177. }
  178. config := &backend.PluginEnableConfig{Timeout: timeout}
  179. return pr.backend.Enable(name, config)
  180. }
  181. func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  182. if err := httputils.ParseForm(r); err != nil {
  183. return err
  184. }
  185. name := vars["name"]
  186. config := &backend.PluginDisableConfig{
  187. ForceDisable: httputils.BoolValue(r, "force"),
  188. }
  189. return pr.backend.Disable(name, config)
  190. }
  191. func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  192. if err := httputils.ParseForm(r); err != nil {
  193. return err
  194. }
  195. name := vars["name"]
  196. config := &backend.PluginRmConfig{
  197. ForceRemove: httputils.BoolValue(r, "force"),
  198. }
  199. return pr.backend.Remove(name, config)
  200. }
  201. func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  202. if err := httputils.ParseForm(r); err != nil {
  203. return errors.Wrap(err, "failed to parse form")
  204. }
  205. metaHeaders, authConfig := parseHeaders(r.Header)
  206. w.Header().Set("Content-Type", "application/json")
  207. output := ioutils.NewWriteFlusher(w)
  208. if err := pr.backend.Push(ctx, vars["name"], metaHeaders, authConfig, output); err != nil {
  209. if !output.Flushed() {
  210. return err
  211. }
  212. _, _ = output.Write(streamformatter.FormatError(err))
  213. }
  214. return nil
  215. }
  216. func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  217. var args []string
  218. if err := httputils.ReadJSON(r, &args); err != nil {
  219. return err
  220. }
  221. if err := pr.backend.Set(vars["name"], args); err != nil {
  222. return err
  223. }
  224. w.WriteHeader(http.StatusNoContent)
  225. return nil
  226. }
  227. func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  228. if err := httputils.ParseForm(r); err != nil {
  229. return err
  230. }
  231. pluginFilters, err := filters.FromJSON(r.Form.Get("filters"))
  232. if err != nil {
  233. return err
  234. }
  235. l, err := pr.backend.List(pluginFilters)
  236. if err != nil {
  237. return err
  238. }
  239. return httputils.WriteJSON(w, http.StatusOK, l)
  240. }
  241. func (pr *pluginRouter) inspectPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  242. result, err := pr.backend.Inspect(vars["name"])
  243. if err != nil {
  244. return err
  245. }
  246. return httputils.WriteJSON(w, http.StatusOK, result)
  247. }