Browse code

Add plugin create functionality.

Signed-off-by: Anusha Ragunathan <anusha@docker.com>

Anusha Ragunathan authored on 2016/10/05 04:01:19
Showing 19 changed files
... ...
@@ -1,9 +1,11 @@
1 1
 package plugin
2 2
 
3 3
 import (
4
+	"io"
4 5
 	"net/http"
5 6
 
6 7
 	enginetypes "github.com/docker/docker/api/types"
8
+	"golang.org/x/net/context"
7 9
 )
8 10
 
9 11
 // Backend for Plugin
... ...
@@ -16,4 +18,5 @@ type Backend interface {
16 16
 	Set(name string, args []string) error
17 17
 	Pull(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error)
18 18
 	Push(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) error
19
+	CreateFromContext(ctx context.Context, tarCtx io.Reader, options *enginetypes.PluginCreateOptions) error
19 20
 }
... ...
@@ -32,5 +32,6 @@ func (r *pluginRouter) initRoutes() {
32 32
 		router.NewPostRoute("/plugins/pull", r.pullPlugin),
33 33
 		router.NewPostRoute("/plugins/{name:.*}/push", r.pushPlugin),
34 34
 		router.NewPostRoute("/plugins/{name:.*}/set", r.setPlugin),
35
+		router.NewPostRoute("/plugins/create", r.createPlugin),
35 36
 	}
36 37
 }
... ...
@@ -40,6 +40,21 @@ func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r
40 40
 	return httputils.WriteJSON(w, http.StatusOK, privileges)
41 41
 }
42 42
 
43
+func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
44
+	if err := httputils.ParseForm(r); err != nil {
45
+		return err
46
+	}
47
+
48
+	options := &types.PluginCreateOptions{
49
+		RepoName: r.FormValue("name")}
50
+
51
+	if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil {
52
+		return err
53
+	}
54
+	w.WriteHeader(http.StatusNoContent)
55
+	return nil
56
+}
57
+
43 58
 func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
44 59
 	return pr.backend.Enable(vars["name"])
45 60
 }
... ...
@@ -6089,6 +6089,33 @@ paths:
6089 6089
           type: "string"
6090 6090
       tags:
6091 6091
         - "Plugins"
6092
+  /plugins/create:
6093
+    post:
6094
+      summary: "Create a plugin"
6095
+      operationId: "PostPluginsCreate"
6096
+      consumes:
6097
+        - "application/x-tar"
6098
+      responses:
6099
+        204:
6100
+          description: "no error"
6101
+        500:
6102
+          description: "server error"
6103
+          schema:
6104
+            $ref: "#/definitions/ErrorResponse"
6105
+      parameters:
6106
+        - name: "name"
6107
+          in: "query"
6108
+          description: "The name of the plugin. The `:latest` tag is optional, and is the default if omitted."
6109
+          required: true
6110
+          type: "string"
6111
+        - name: "tarContext"
6112
+          in: "body"
6113
+          description: "Path to tar containing plugin rootfs and manifest"
6114
+          schema:
6115
+            type: "string"
6116
+            format: "binary"
6117
+      tags:
6118
+        - "Plugins"
6092 6119
   /nodes:
6093 6120
     get:
6094 6121
       summary: "List nodes"
... ...
@@ -338,3 +338,8 @@ type PluginInstallOptions struct {
338 338
 	PrivilegeFunc         RequestPrivilegeFunc
339 339
 	AcceptPermissionsFunc func(PluginPrivileges) (bool, error)
340 340
 }
341
+
342
+// PluginCreateOptions hold all options to plugin create.
343
+type PluginCreateOptions struct {
344
+	RepoName string
345
+}
... ...
@@ -29,6 +29,7 @@ func NewPluginCommand(dockerCli *command.DockerCli) *cobra.Command {
29 29
 		newRemoveCommand(dockerCli),
30 30
 		newSetCommand(dockerCli),
31 31
 		newPushCommand(dockerCli),
32
+		newCreateCommand(dockerCli),
32 33
 	)
33 34
 	return cmd
34 35
 }
35 36
new file mode 100644
... ...
@@ -0,0 +1,125 @@
0
+package plugin
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"io"
6
+	"os"
7
+	"path/filepath"
8
+
9
+	"github.com/Sirupsen/logrus"
10
+	"github.com/docker/docker/api/types"
11
+	"github.com/docker/docker/cli"
12
+	"github.com/docker/docker/cli/command"
13
+	"github.com/docker/docker/pkg/archive"
14
+	"github.com/docker/docker/reference"
15
+	"github.com/spf13/cobra"
16
+	"golang.org/x/net/context"
17
+)
18
+
19
+// validateTag checks if the given repoName can be resolved.
20
+func validateTag(rawRepo string) error {
21
+	_, err := reference.ParseNamed(rawRepo)
22
+
23
+	return err
24
+}
25
+
26
+// validateManifest ensures that a valid manifest.json is available in the given path
27
+func validateManifest(path string) error {
28
+	dt, err := os.Open(filepath.Join(path, "manifest.json"))
29
+	if err != nil {
30
+		return err
31
+	}
32
+
33
+	m := types.PluginManifest{}
34
+	err = json.NewDecoder(dt).Decode(&m)
35
+	dt.Close()
36
+
37
+	return err
38
+}
39
+
40
+// validateContextDir validates the given dir and returns abs path on success.
41
+func validateContextDir(contextDir string) (string, error) {
42
+	absContextDir, err := filepath.Abs(contextDir)
43
+
44
+	stat, err := os.Lstat(absContextDir)
45
+	if err != nil {
46
+		return "", err
47
+	}
48
+
49
+	if !stat.IsDir() {
50
+		return "", fmt.Errorf("context must be a directory")
51
+	}
52
+
53
+	return absContextDir, nil
54
+}
55
+
56
+type pluginCreateOptions struct {
57
+	repoName string
58
+	context  string
59
+	compress bool
60
+}
61
+
62
+func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
63
+	options := pluginCreateOptions{}
64
+
65
+	cmd := &cobra.Command{
66
+		Use:   "create [OPTIONS] reponame[:tag] PATH-TO-ROOTFS (rootfs + manifest.json)",
67
+		Short: "Create a plugin from a rootfs and manifest",
68
+		Args:  cli.RequiresMinArgs(2),
69
+		RunE: func(cmd *cobra.Command, args []string) error {
70
+			options.repoName = args[0]
71
+			options.context = args[1]
72
+			return runCreate(dockerCli, options)
73
+		},
74
+	}
75
+
76
+	flags := cmd.Flags()
77
+
78
+	flags.BoolVar(&options.compress, "compress", false, "Compress the context using gzip")
79
+
80
+	return cmd
81
+}
82
+
83
+func runCreate(dockerCli *command.DockerCli, options pluginCreateOptions) error {
84
+	var (
85
+		createCtx io.ReadCloser
86
+		err       error
87
+	)
88
+
89
+	if err := validateTag(options.repoName); err != nil {
90
+		return err
91
+	}
92
+
93
+	absContextDir, err := validateContextDir(options.context)
94
+	if err != nil {
95
+		return err
96
+	}
97
+
98
+	if err := validateManifest(options.context); err != nil {
99
+		return err
100
+	}
101
+
102
+	compression := archive.Uncompressed
103
+	if options.compress {
104
+		logrus.Debugf("compression enabled")
105
+		compression = archive.Gzip
106
+	}
107
+
108
+	createCtx, err = archive.TarWithOptions(absContextDir, &archive.TarOptions{
109
+		Compression: compression,
110
+	})
111
+
112
+	if err != nil {
113
+		return err
114
+	}
115
+
116
+	ctx := context.Background()
117
+
118
+	createOptions := types.PluginCreateOptions{RepoName: options.repoName}
119
+	if err = dockerCli.Client().PluginCreate(ctx, createCtx, createOptions); err != nil {
120
+		return err
121
+	}
122
+	fmt.Fprintln(dockerCli.Out(), options.repoName)
123
+	return nil
124
+}
... ...
@@ -1,6 +1,8 @@
1 1
 package client
2 2
 
3 3
 import (
4
+	"io"
5
+
4 6
 	"github.com/docker/docker/api/types"
5 7
 	"golang.org/x/net/context"
6 8
 )
... ...
@@ -27,4 +29,5 @@ type PluginAPIClient interface {
27 27
 	PluginPush(ctx context.Context, name string, registryAuth string) error
28 28
 	PluginSet(ctx context.Context, name string, args []string) error
29 29
 	PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error)
30
+	PluginCreate(ctx context.Context, createContext io.Reader, options types.PluginCreateOptions) error
30 31
 }
31 32
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package client
1
+
2
+import (
3
+	"io"
4
+	"net/http"
5
+	"net/url"
6
+
7
+	"github.com/docker/docker/api/types"
8
+	"golang.org/x/net/context"
9
+)
10
+
11
+// PluginCreate creates a plugin
12
+func (cli *Client) PluginCreate(ctx context.Context, createContext io.Reader, createOptions types.PluginCreateOptions) error {
13
+	headers := http.Header(make(map[string][]string))
14
+	headers.Set("Content-Type", "application/tar")
15
+
16
+	query := url.Values{}
17
+	query.Set("name", createOptions.RepoName)
18
+
19
+	resp, err := cli.postRaw(ctx, "/plugins/create", query, createContext, headers)
20
+	if err != nil {
21
+		return err
22
+	}
23
+	ensureReaderClosed(resp)
24
+	return err
25
+}
... ...
@@ -4401,6 +4401,44 @@ Content-Type: text/plain; charset=utf-8
4401 4401
 -   **404** - plugin not installed
4402 4402
 -   **500** - plugin is active
4403 4403
 
4404
+### Create a plugin
4405
+
4406
+`POST /v1.25/plugins/create?name=(plugin name)`
4407
+
4408
+Create a plugin
4409
+
4410
+**Example request**:
4411
+
4412
+To create a plugin named `plugin`
4413
+
4414
+```
4415
+POST /v1.25/plugins/create?name=plugin:latest HTTP/1.1
4416
+Content-Type: application/x-tar
4417
+
4418
+{% raw %}
4419
+{{ TAR STREAM }}
4420
+{% endraw %}
4421
+```
4422
+
4423
+The `:latest` tag is optional, and is used as default if omitted.
4424
+
4425
+**Example response**:
4426
+
4427
+```
4428
+HTTP/1.1 204 No Content
4429
+Content-Length: 0
4430
+Content-Type: text/plain; charset=utf-8
4431
+```
4432
+
4433
+**Query parameters**:
4434
+
4435
+- **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.
4436
+
4437
+**Status codes**:
4438
+
4439
+-   **204** - no error
4440
+-   **500** - server error
4441
+
4404 4442
 <!-- TODO Document "docker plugin push" endpoint once we have "plugin build"
4405 4443
 
4406 4444
 ### Push a plugin
4407 4445
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+---
1
+title: "plugin create (experimental)"
2
+description: "the plugin create command description and usage"
3
+keywords: "plugin, create"
4
+advisory: "experimental"
5
+---
6
+
7
+<!-- This file is maintained within the docker/docker Github
8
+     repository at https://github.com/docker/docker/. Make all
9
+     pull requests against that repo. If you see this file in
10
+     another repository, consider it read-only there, as it will
11
+     periodically be overwritten by the definitive file. Pull
12
+     requests which include edits to this file in other repositories
13
+     will be rejected.
14
+-->
15
+
16
+```markdown
17
+Usage:  docker plugin create [OPTIONS] reponame[:tag] PATH-TO-ROOTFS
18
+
19
+create a plugin from the given PATH-TO-ROOTFS, which contains the plugin's root filesystem and the manifest file, manifest.json
20
+
21
+Options:
22
+      --compress   Compress the context using gzip 
23
+      --help       Print usage
24
+```
25
+
26
+Creates a plugin. Before creating the plugin, prepare the plugin's root filesystem as well as
27
+the manifest.json (https://github.com/docker/docker/blob/master/docs/extend/manifest.md)
28
+
29
+
30
+The following example shows how to create a sample `plugin`.
31
+
32
+```bash
33
+
34
+$ ls -ls /home/pluginDir
35
+
36
+4 -rw-r--r--  1 root root 431 Nov  7 01:40 manifest.json
37
+0 drwxr-xr-x 19 root root 420 Nov  7 01:40 rootfs
38
+
39
+$ docker plugin create plugin /home/pluginDir
40
+plugin
41
+
42
+NAME                  	TAG                 DESCRIPTION                  ENABLED
43
+plugin                  latest              A sample plugin for Docker   true
44
+```
45
+
46
+The plugin can subsequently be enabled for local use or pushed to the public registry.
47
+
48
+## Related information
49
+
50
+* [plugin ls](plugin_ls.md)
51
+* [plugin enable](plugin_enable.md)
52
+* [plugin disable](plugin_disable.md)
53
+* [plugin inspect](plugin_inspect.md)
54
+* [plugin install](plugin_install.md)
55
+* [plugin rm](plugin_rm.md)
56
+* [plugin set](plugin_set.md)
... ...
@@ -55,6 +55,7 @@ tiborvass/no-remove   latest              A test plugin for Docker   false
55 55
 ## Related information
56 56
 
57 57
 * [plugin ls](plugin_ls.md)
58
+* [plugin create](plugin_create.md)
58 59
 * [plugin enable](plugin_enable.md)
59 60
 * [plugin inspect](plugin_inspect.md)
60 61
 * [plugin install](plugin_install.md)
... ...
@@ -54,6 +54,7 @@ tiborvass/no-remove   latest              A test plugin for Docker   true
54 54
 
55 55
 ## Related information
56 56
 
57
+* [plugin create](plugin_create.md)
57 58
 * [plugin ls](plugin_ls.md)
58 59
 * [plugin disable](plugin_disable.md)
59 60
 * [plugin inspect](plugin_inspect.md)
60 61
old mode 100755
61 62
new mode 100644
... ...
@@ -154,6 +154,7 @@ $ docker plugin inspect -f '{{.Id}}' tiborvass/no-remove:latest
154 154
 
155 155
 ## Related information
156 156
 
157
+* [plugin create](plugin_create.md)
157 158
 * [plugin ls](plugin_ls.md)
158 159
 * [plugin enable](plugin_enable.md)
159 160
 * [plugin disable](plugin_disable.md)
... ...
@@ -59,6 +59,7 @@ tiborvass/no-remove   latest              A test plugin for Docker   true
59 59
 
60 60
 ## Related information
61 61
 
62
+* [plugin create](plugin_create.md)
62 63
 * [plugin ls](plugin_ls.md)
63 64
 * [plugin enable](plugin_enable.md)
64 65
 * [plugin disable](plugin_disable.md)
... ...
@@ -43,6 +43,7 @@ tiborvass/no-remove   latest              A test plugin for Docker   true
43 43
 
44 44
 ## Related information
45 45
 
46
+* [plugin create](plugin_create.md)
46 47
 * [plugin enable](plugin_enable.md)
47 48
 * [plugin disable](plugin_disable.md)
48 49
 * [plugin inspect](plugin_inspect.md)
... ...
@@ -46,6 +46,7 @@ tiborvass/no-remove
46 46
 
47 47
 ## Related information
48 48
 
49
+* [plugin create](plugin_create.md)
49 50
 * [plugin ls](plugin_ls.md)
50 51
 * [plugin enable](plugin_enable.md)
51 52
 * [plugin disable](plugin_disable.md)
... ...
@@ -43,6 +43,7 @@ $ docker plugin inspect -f {{.Config.Env}} tiborvass/no-remove
43 43
 
44 44
 ## Related information
45 45
 
46
+* [plugin create](plugin_create.md)
46 47
 * [plugin ls](plugin_ls.md)
47 48
 * [plugin enable](plugin_enable.md)
48 49
 * [plugin disable](plugin_disable.md)
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"io"
7 8
 	"io/ioutil"
8 9
 	"net/http"
9 10
 	"os"
... ...
@@ -12,9 +13,11 @@ import (
12 12
 	"github.com/Sirupsen/logrus"
13 13
 	"github.com/docker/docker/api/types"
14 14
 	"github.com/docker/docker/pkg/archive"
15
+	"github.com/docker/docker/pkg/chrootarchive"
15 16
 	"github.com/docker/docker/pkg/stringid"
16 17
 	"github.com/docker/docker/plugin/distribution"
17 18
 	"github.com/docker/docker/plugin/v2"
19
+	"golang.org/x/net/context"
18 20
 )
19 21
 
20 22
 // Disable deactivates a plugin, which implies that they cannot be used by containers.
... ...
@@ -174,3 +177,37 @@ func (pm *Manager) Set(name string, args []string) error {
174 174
 	}
175 175
 	return p.Set(args)
176 176
 }
177
+
178
+// CreateFromContext creates a plugin from the given pluginDir which contains
179
+// both the rootfs and the manifest.json and a repoName with optional tag.
180
+func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.Reader, options *types.PluginCreateOptions) error {
181
+	pluginID := stringid.GenerateNonCryptoID()
182
+
183
+	pluginDir := filepath.Join(pm.libRoot, pluginID)
184
+	if err := os.MkdirAll(pluginDir, 0755); err != nil {
185
+		return err
186
+	}
187
+
188
+	if err := chrootarchive.Untar(tarCtx, pluginDir, nil); err != nil {
189
+		return err
190
+	}
191
+
192
+	repoName := options.RepoName
193
+	ref, err := distribution.GetRef(repoName)
194
+	if err != nil {
195
+		return err
196
+	}
197
+	name := ref.Name()
198
+	tag := distribution.GetTag(ref)
199
+
200
+	p := v2.NewPlugin(name, pluginID, pm.runRoot, pm.libRoot, tag)
201
+	if err := p.InitPlugin(); err != nil {
202
+		return err
203
+	}
204
+
205
+	pm.pluginStore.Add(p)
206
+
207
+	pm.pluginEventLogger(p.GetID(), repoName, "create")
208
+
209
+	return nil
210
+}