|
@@ -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
|
|
|
+}
|