Ver código fonte

Merge pull request #28164 from anusha-ragunathan/plugin-build

Add plugin create functionality.
Victor Vieux 8 anos atrás
pai
commit
28a1ea342d

+ 3 - 0
api/server/router/plugin/backend.go

@@ -1,9 +1,11 @@
 package plugin
 
 import (
+	"io"
 	"net/http"
 
 	enginetypes "github.com/docker/docker/api/types"
+	"golang.org/x/net/context"
 )
 
 // Backend for Plugin
@@ -16,4 +18,5 @@ type Backend interface {
 	Set(name string, args []string) error
 	Pull(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error)
 	Push(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) error
+	CreateFromContext(ctx context.Context, tarCtx io.Reader, options *enginetypes.PluginCreateOptions) error
 }

+ 1 - 0
api/server/router/plugin/plugin.go

@@ -32,5 +32,6 @@ func (r *pluginRouter) initRoutes() {
 		router.NewPostRoute("/plugins/pull", r.pullPlugin),
 		router.NewPostRoute("/plugins/{name:.*}/push", r.pushPlugin),
 		router.NewPostRoute("/plugins/{name:.*}/set", r.setPlugin),
+		router.NewPostRoute("/plugins/create", r.createPlugin),
 	}
 }

+ 15 - 0
api/server/router/plugin/plugin_routes.go

@@ -40,6 +40,21 @@ func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r
 	return httputils.WriteJSON(w, http.StatusOK, privileges)
 }
 
+func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+
+	options := &types.PluginCreateOptions{
+		RepoName: r.FormValue("name")}
+
+	if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil {
+		return err
+	}
+	w.WriteHeader(http.StatusNoContent)
+	return nil
+}
+
 func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	return pr.backend.Enable(vars["name"])
 }

+ 27 - 0
api/swagger.yaml

@@ -6089,6 +6089,33 @@ paths:
           type: "string"
       tags:
         - "Plugins"
+  /plugins/create:
+    post:
+      summary: "Create a plugin"
+      operationId: "PostPluginsCreate"
+      consumes:
+        - "application/x-tar"
+      responses:
+        204:
+          description: "no error"
+        500:
+          description: "server error"
+          schema:
+            $ref: "#/definitions/ErrorResponse"
+      parameters:
+        - name: "name"
+          in: "query"
+          description: "The name of the plugin. The `:latest` tag is optional, and is the default if omitted."
+          required: true
+          type: "string"
+        - name: "tarContext"
+          in: "body"
+          description: "Path to tar containing plugin rootfs and manifest"
+          schema:
+            type: "string"
+            format: "binary"
+      tags:
+        - "Plugins"
   /nodes:
     get:
       summary: "List nodes"

+ 5 - 0
api/types/client.go

@@ -356,3 +356,8 @@ type SwarmUnlockKeyResponse struct {
 	// UnlockKey is the unlock key in ASCII-armored format.
 	UnlockKey string
 }
+
+// PluginCreateOptions hold all options to plugin create.
+type PluginCreateOptions struct {
+	RepoName string
+}

+ 1 - 0
cli/command/plugin/cmd.go

@@ -28,6 +28,7 @@ func NewPluginCommand(dockerCli *command.DockerCli) *cobra.Command {
 		newRemoveCommand(dockerCli),
 		newSetCommand(dockerCli),
 		newPushCommand(dockerCli),
+		newCreateCommand(dockerCli),
 	)
 	return cmd
 }

+ 125 - 0
cli/command/plugin/create.go

@@ -0,0 +1,125 @@
+package plugin
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/cli/command"
+	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/reference"
+	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
+)
+
+// validateTag checks if the given repoName can be resolved.
+func validateTag(rawRepo string) error {
+	_, err := reference.ParseNamed(rawRepo)
+
+	return err
+}
+
+// validateManifest ensures that a valid manifest.json is available in the given path
+func validateManifest(path string) error {
+	dt, err := os.Open(filepath.Join(path, "manifest.json"))
+	if err != nil {
+		return err
+	}
+
+	m := types.PluginManifest{}
+	err = json.NewDecoder(dt).Decode(&m)
+	dt.Close()
+
+	return err
+}
+
+// validateContextDir validates the given dir and returns abs path on success.
+func validateContextDir(contextDir string) (string, error) {
+	absContextDir, err := filepath.Abs(contextDir)
+
+	stat, err := os.Lstat(absContextDir)
+	if err != nil {
+		return "", err
+	}
+
+	if !stat.IsDir() {
+		return "", fmt.Errorf("context must be a directory")
+	}
+
+	return absContextDir, nil
+}
+
+type pluginCreateOptions struct {
+	repoName string
+	context  string
+	compress bool
+}
+
+func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
+	options := pluginCreateOptions{}
+
+	cmd := &cobra.Command{
+		Use:   "create [OPTIONS] reponame[:tag] PATH-TO-ROOTFS (rootfs + manifest.json)",
+		Short: "Create a plugin from a rootfs and manifest",
+		Args:  cli.RequiresMinArgs(2),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			options.repoName = args[0]
+			options.context = args[1]
+			return runCreate(dockerCli, options)
+		},
+	}
+
+	flags := cmd.Flags()
+
+	flags.BoolVar(&options.compress, "compress", false, "Compress the context using gzip")
+
+	return cmd
+}
+
+func runCreate(dockerCli *command.DockerCli, options pluginCreateOptions) error {
+	var (
+		createCtx io.ReadCloser
+		err       error
+	)
+
+	if err := validateTag(options.repoName); err != nil {
+		return err
+	}
+
+	absContextDir, err := validateContextDir(options.context)
+	if err != nil {
+		return err
+	}
+
+	if err := validateManifest(options.context); err != nil {
+		return err
+	}
+
+	compression := archive.Uncompressed
+	if options.compress {
+		logrus.Debugf("compression enabled")
+		compression = archive.Gzip
+	}
+
+	createCtx, err = archive.TarWithOptions(absContextDir, &archive.TarOptions{
+		Compression: compression,
+	})
+
+	if err != nil {
+		return err
+	}
+
+	ctx := context.Background()
+
+	createOptions := types.PluginCreateOptions{RepoName: options.repoName}
+	if err = dockerCli.Client().PluginCreate(ctx, createCtx, createOptions); err != nil {
+		return err
+	}
+	fmt.Fprintln(dockerCli.Out(), options.repoName)
+	return nil
+}

+ 3 - 0
client/interface_experimental.go

@@ -1,6 +1,8 @@
 package client
 
 import (
+	"io"
+
 	"github.com/docker/docker/api/types"
 	"golang.org/x/net/context"
 )
@@ -27,4 +29,5 @@ type PluginAPIClient interface {
 	PluginPush(ctx context.Context, name string, registryAuth string) error
 	PluginSet(ctx context.Context, name string, args []string) error
 	PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error)
+	PluginCreate(ctx context.Context, createContext io.Reader, options types.PluginCreateOptions) error
 }

+ 26 - 0
client/plugin_create.go

@@ -0,0 +1,26 @@
+package client
+
+import (
+	"io"
+	"net/http"
+	"net/url"
+
+	"github.com/docker/docker/api/types"
+	"golang.org/x/net/context"
+)
+
+// PluginCreate creates a plugin
+func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, createOptions types.PluginCreateOptions) error {
+	headers := http.Header(make(map[string][]string))
+	headers.Set("Content-Type", "application/tar")
+
+	query := url.Values{}
+	query.Set("name", createOptions.RepoName)
+
+	resp, err := cli.postRaw(ctx, "/plugins/create", query, createContext, headers)
+	if err != nil {
+		return err
+	}
+	ensureReaderClosed(resp)
+	return err
+}

+ 38 - 0
docs/reference/api/docker_remote_api_v1.25.md

@@ -4400,6 +4400,44 @@ Content-Type: text/plain; charset=utf-8
 -   **404** - plugin not installed
 -   **500** - plugin is active
 
+### Create a plugin
+
+`POST /v1.25/plugins/create?name=(plugin name)`
+
+Create a plugin
+
+**Example request**:
+
+To create a plugin named `plugin`
+
+```
+POST /v1.25/plugins/create?name=plugin:latest HTTP/1.1
+Content-Type: application/x-tar
+
+{% raw %}
+{{ TAR STREAM }}
+{% endraw %}
+```
+
+The `:latest` tag is optional, and is used as default if omitted.
+
+**Example response**:
+
+```
+HTTP/1.1 204 No Content
+Content-Length: 0
+Content-Type: text/plain; charset=utf-8
+```
+
+**Query parameters**:
+
+- **name** - A name and optional tag to apply for the plugin in the `name:tag format`. If you omit the `tag` the default `:latest` value is assumed.
+
+**Status codes**:
+
+-   **204** - no error
+-   **500** - server error
+
 <!-- TODO Document "docker plugin push" endpoint once we have "plugin build"
 
 ### Push a plugin

+ 57 - 0
docs/reference/commandline/plugin_create.md

@@ -0,0 +1,57 @@
+---
+title: "plugin create (experimental)"
+description: "the plugin create command description and usage"
+keywords: "plugin, create"
+advisory: "experimental"
+---
+
+<!-- This file is maintained within the docker/docker Github
+     repository at https://github.com/docker/docker/. Make all
+     pull requests against that repo. If you see this file in
+     another repository, consider it read-only there, as it will
+     periodically be overwritten by the definitive file. Pull
+     requests which include edits to this file in other repositories
+     will be rejected.
+-->
+
+```markdown
+Usage:  docker plugin create [OPTIONS] reponame[:tag] PATH-TO-ROOTFS
+
+create a plugin from the given PATH-TO-ROOTFS, which contains the plugin's root filesystem and the manifest file, manifest.json
+
+Options:
+      --compress   Compress the context using gzip 
+      --help       Print usage
+```
+
+Creates a plugin. Before creating the plugin, prepare the plugin's root filesystem as well as
+the manifest.json (https://github.com/docker/docker/blob/master/docs/extend/manifest.md)
+
+
+The following example shows how to create a sample `plugin`.
+
+```bash
+
+$ ls -ls /home/pluginDir
+
+4 -rw-r--r--  1 root root 431 Nov  7 01:40 manifest.json
+0 drwxr-xr-x 19 root root 420 Nov  7 01:40 rootfs
+
+$ docker plugin create plugin /home/pluginDir
+plugin
+
+NAME                  	TAG                 DESCRIPTION                  ENABLED
+plugin                  latest              A sample plugin for Docker   true
+```
+
+The plugin can subsequently be enabled for local use or pushed to the public registry.
+
+## Related information
+
+* [plugin ls](plugin_ls.md)
+* [plugin enable](plugin_enable.md)
+* [plugin disable](plugin_disable.md)
+* [plugin inspect](plugin_inspect.md)
+* [plugin install](plugin_install.md)
+* [plugin rm](plugin_rm.md)
+* [plugin set](plugin_set.md)

+ 1 - 0
docs/reference/commandline/plugin_disable.md

@@ -55,6 +55,7 @@ tiborvass/no-remove   latest              A test plugin for Docker   false
 ## Related information
 
 * [plugin ls](plugin_ls.md)
+* [plugin create](plugin_create.md)
 * [plugin enable](plugin_enable.md)
 * [plugin inspect](plugin_inspect.md)
 * [plugin install](plugin_install.md)

+ 1 - 0
docs/reference/commandline/plugin_enable.md

@@ -54,6 +54,7 @@ tiborvass/no-remove   latest              A test plugin for Docker   true
 
 ## Related information
 
+* [plugin create](plugin_create.md)
 * [plugin ls](plugin_ls.md)
 * [plugin disable](plugin_disable.md)
 * [plugin inspect](plugin_inspect.md)

+ 1 - 0
docs/reference/commandline/plugin_inspect.md

@@ -154,6 +154,7 @@ $ docker plugin inspect -f '{{.Id}}' tiborvass/no-remove:latest
 
 ## Related information
 
+* [plugin create](plugin_create.md)
 * [plugin ls](plugin_ls.md)
 * [plugin enable](plugin_enable.md)
 * [plugin disable](plugin_disable.md)

+ 1 - 0
docs/reference/commandline/plugin_install.md

@@ -60,6 +60,7 @@ tiborvass/no-remove   latest              A test plugin for Docker   true
 
 ## Related information
 
+* [plugin create](plugin_create.md)
 * [plugin ls](plugin_ls.md)
 * [plugin enable](plugin_enable.md)
 * [plugin disable](plugin_disable.md)

+ 1 - 0
docs/reference/commandline/plugin_ls.md

@@ -43,6 +43,7 @@ tiborvass/no-remove   latest              A test plugin for Docker   true
 
 ## Related information
 
+* [plugin create](plugin_create.md)
 * [plugin enable](plugin_enable.md)
 * [plugin disable](plugin_disable.md)
 * [plugin inspect](plugin_inspect.md)

+ 1 - 0
docs/reference/commandline/plugin_rm.md

@@ -46,6 +46,7 @@ tiborvass/no-remove
 
 ## Related information
 
+* [plugin create](plugin_create.md)
 * [plugin ls](plugin_ls.md)
 * [plugin enable](plugin_enable.md)
 * [plugin disable](plugin_disable.md)

+ 1 - 0
docs/reference/commandline/plugin_set.md

@@ -43,6 +43,7 @@ $ docker plugin inspect -f {{.Config.Env}} tiborvass/no-remove
 
 ## Related information
 
+* [plugin create](plugin_create.md)
 * [plugin ls](plugin_ls.md)
 * [plugin enable](plugin_enable.md)
 * [plugin disable](plugin_disable.md)

+ 37 - 0
plugin/backend.go

@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"net/http"
 	"os"
@@ -12,9 +13,11 @@ import (
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/pkg/chrootarchive"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/plugin/distribution"
 	"github.com/docker/docker/plugin/v2"
+	"golang.org/x/net/context"
 )
 
 // Disable deactivates a plugin, which implies that they cannot be used by containers.
@@ -175,3 +178,37 @@ func (pm *Manager) Set(name string, args []string) error {
 	}
 	return p.Set(args)
 }
+
+// CreateFromContext creates a plugin from the given pluginDir which contains
+// both the rootfs and the manifest.json and a repoName with optional tag.
+func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.Reader, options *types.PluginCreateOptions) error {
+	pluginID := stringid.GenerateNonCryptoID()
+
+	pluginDir := filepath.Join(pm.libRoot, pluginID)
+	if err := os.MkdirAll(pluginDir, 0755); err != nil {
+		return err
+	}
+
+	if err := chrootarchive.Untar(tarCtx, pluginDir, nil); err != nil {
+		return err
+	}
+
+	repoName := options.RepoName
+	ref, err := distribution.GetRef(repoName)
+	if err != nil {
+		return err
+	}
+	name := ref.Name()
+	tag := distribution.GetTag(ref)
+
+	p := v2.NewPlugin(name, pluginID, pm.runRoot, pm.libRoot, tag)
+	if err := p.InitPlugin(); err != nil {
+		return err
+	}
+
+	pm.pluginStore.Add(p)
+
+	pm.pluginEventLogger(p.GetID(), repoName, "create")
+
+	return nil
+}