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