github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/plugin/create.go (about)

     1  package plugin
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/docker/cli/cli"
    12  	"github.com/docker/cli/cli/command"
    13  	"github.com/docker/distribution/reference"
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/pkg/archive"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  	"github.com/spf13/cobra"
    19  )
    20  
    21  // validateTag checks if the given repoName can be resolved.
    22  func validateTag(rawRepo string) error {
    23  	_, err := reference.ParseNormalizedNamed(rawRepo)
    24  
    25  	return err
    26  }
    27  
    28  // validateConfig ensures that a valid config.json is available in the given path
    29  func validateConfig(path string) error {
    30  	dt, err := os.Open(filepath.Join(path, "config.json"))
    31  	if err != nil {
    32  		return err
    33  	}
    34  
    35  	m := types.PluginConfig{}
    36  	err = json.NewDecoder(dt).Decode(&m)
    37  	dt.Close()
    38  
    39  	return err
    40  }
    41  
    42  // validateContextDir validates the given dir and returns abs path on success.
    43  func validateContextDir(contextDir string) (string, error) {
    44  	absContextDir, err := filepath.Abs(contextDir)
    45  	if err != nil {
    46  		return "", err
    47  	}
    48  	stat, err := os.Lstat(absContextDir)
    49  	if err != nil {
    50  		return "", err
    51  	}
    52  
    53  	if !stat.IsDir() {
    54  		return "", errors.Errorf("context must be a directory")
    55  	}
    56  
    57  	return absContextDir, nil
    58  }
    59  
    60  type pluginCreateOptions struct {
    61  	repoName string
    62  	context  string
    63  	compress bool
    64  }
    65  
    66  func newCreateCommand(dockerCli command.Cli) *cobra.Command {
    67  	options := pluginCreateOptions{}
    68  
    69  	cmd := &cobra.Command{
    70  		Use:   "create [OPTIONS] PLUGIN PLUGIN-DATA-DIR",
    71  		Short: "Create a plugin from a rootfs and configuration. Plugin data directory must contain config.json and rootfs directory.",
    72  		Args:  cli.RequiresMinArgs(2),
    73  		RunE: func(cmd *cobra.Command, args []string) error {
    74  			options.repoName = args[0]
    75  			options.context = args[1]
    76  			return runCreate(dockerCli, options)
    77  		},
    78  	}
    79  
    80  	flags := cmd.Flags()
    81  
    82  	flags.BoolVar(&options.compress, "compress", false, "Compress the context using gzip")
    83  
    84  	return cmd
    85  }
    86  
    87  func runCreate(dockerCli command.Cli, options pluginCreateOptions) error {
    88  	var (
    89  		createCtx io.ReadCloser
    90  		err       error
    91  	)
    92  
    93  	if err := validateTag(options.repoName); err != nil {
    94  		return err
    95  	}
    96  
    97  	absContextDir, err := validateContextDir(options.context)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	if err := validateConfig(options.context); err != nil {
   103  		return err
   104  	}
   105  
   106  	compression := archive.Uncompressed
   107  	if options.compress {
   108  		logrus.Debugf("compression enabled")
   109  		compression = archive.Gzip
   110  	}
   111  
   112  	createCtx, err = archive.TarWithOptions(absContextDir, &archive.TarOptions{
   113  		Compression: compression,
   114  	})
   115  
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	ctx := context.Background()
   121  
   122  	createOptions := types.PluginCreateOptions{RepoName: options.repoName}
   123  	if err = dockerCli.Client().PluginCreate(ctx, createCtx, createOptions); err != nil {
   124  		return err
   125  	}
   126  	fmt.Fprintln(dockerCli.Out(), options.repoName)
   127  	return nil
   128  }