mirror of https://github.com/docker/cli.git
Add basic framework for writing a CLI plugin
That is, the helper to be used from the plugin's `main`. Also add a `helloworld` plugin example and build integration. Signed-off-by: Ian Campbell <ijc@docker.com>
This commit is contained in:
parent
8cf946d1bc
commit
e96240427f
12
Makefile
12
Makefile
|
@ -34,6 +34,10 @@ binary: ## build executable for Linux
|
|||
@echo "WARNING: binary creates a Linux executable. Use cross for macOS or Windows."
|
||||
./scripts/build/binary
|
||||
|
||||
.PHONY: plugins
|
||||
plugins: ## build example CLI plugins
|
||||
./scripts/build/plugins
|
||||
|
||||
.PHONY: cross
|
||||
cross: ## build executable for macOS and Windows
|
||||
./scripts/build/cross
|
||||
|
@ -42,10 +46,18 @@ cross: ## build executable for macOS and Windows
|
|||
binary-windows: ## build executable for Windows
|
||||
./scripts/build/windows
|
||||
|
||||
.PHONY: plugins-windows
|
||||
plugins-windows: ## build example CLI plugins for Windows
|
||||
./scripts/build/plugins-windows
|
||||
|
||||
.PHONY: binary-osx
|
||||
binary-osx: ## build executable for macOS
|
||||
./scripts/build/osx
|
||||
|
||||
.PHONY: plugins-osx
|
||||
plugins-osx: ## build example CLI plugins for macOS
|
||||
./scripts/build/plugins-osx
|
||||
|
||||
.PHONY: dynbinary
|
||||
dynbinary: ## build dynamically linked binary
|
||||
./scripts/build/dynbinary
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
||||
goodbye := &cobra.Command{
|
||||
Use: "goodbye",
|
||||
Short: "Say Goodbye instead of Hello",
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
fmt.Fprintln(dockerCli.Out(), "Goodbye World!")
|
||||
},
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "helloworld",
|
||||
Short: "A basic Hello World plugin for tests",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Fprintln(dockerCli.Out(), "Hello World!")
|
||||
},
|
||||
}
|
||||
|
||||
cmd.AddCommand(goodbye)
|
||||
return cmd
|
||||
},
|
||||
manager.Metadata{
|
||||
SchemaVersion: "0.1.0",
|
||||
Vendor: "Docker Inc.",
|
||||
Version: "0.1.0",
|
||||
})
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package manager
|
||||
|
||||
const (
|
||||
// NamePrefix is the prefix required on all plugin binary names
|
||||
NamePrefix = "docker-"
|
||||
|
||||
// MetadataSubcommandName is the name of the plugin subcommand
|
||||
// which must be supported by every plugin and returns the
|
||||
// plugin metadata.
|
||||
MetadataSubcommandName = "docker-cli-plugin-metadata"
|
||||
)
|
||||
|
||||
// Metadata provided by the plugin
|
||||
type Metadata struct {
|
||||
// SchemaVersion describes the version of this struct. Mandatory, must be "0.1.0"
|
||||
SchemaVersion string
|
||||
// Vendor is the name of the plugin vendor. Mandatory
|
||||
Vendor string
|
||||
// Version is the optional version of this plugin.
|
||||
Version string
|
||||
// ShortDescription should be suitable for a single line help message.
|
||||
ShortDescription string
|
||||
// URL is a pointer to the plugin's homepage.
|
||||
URL string
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Run is the top-level entry point to the CLI plugin framework. It should be called from your plugin's `main()` function.
|
||||
func Run(makeCmd func(command.Cli) *cobra.Command, meta manager.Metadata) {
|
||||
dockerCli, err := command.NewDockerCli()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
plugin := makeCmd(dockerCli)
|
||||
|
||||
cmd := newPluginCommand(dockerCli, plugin, meta)
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
if sterr, ok := err.(cli.StatusError); ok {
|
||||
if sterr.Status != "" {
|
||||
fmt.Fprintln(dockerCli.Err(), sterr.Status)
|
||||
}
|
||||
// StatusError should only be used for errors, and all errors should
|
||||
// have a non-zero exit status, so never exit with 0
|
||||
if sterr.StatusCode == 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(sterr.StatusCode)
|
||||
}
|
||||
fmt.Fprintln(dockerCli.Err(), err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager.Metadata) *cobra.Command {
|
||||
var (
|
||||
opts *cliflags.ClientOptions
|
||||
flags *pflag.FlagSet
|
||||
)
|
||||
|
||||
name := plugin.Use
|
||||
fullname := manager.NamePrefix + name
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: fmt.Sprintf("docker [OPTIONS] %s [ARG...]", name),
|
||||
Short: fullname + " is a Docker CLI plugin",
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
TraverseChildren: true,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
// flags must be the top-level command flags, not cmd.Flags()
|
||||
opts.Common.SetDefaultOptions(flags)
|
||||
return dockerCli.Initialize(opts)
|
||||
},
|
||||
DisableFlagsInUseLine: true,
|
||||
}
|
||||
opts, flags = cli.SetupPluginRootCommand(cmd)
|
||||
|
||||
cmd.SetOutput(dockerCli.Out())
|
||||
|
||||
cmd.AddCommand(
|
||||
plugin,
|
||||
newMetadataSubcommand(plugin, meta),
|
||||
)
|
||||
|
||||
cli.DisableFlagsInUseLine(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newMetadataSubcommand(plugin *cobra.Command, meta manager.Metadata) *cobra.Command {
|
||||
if meta.ShortDescription == "" {
|
||||
meta.ShortDescription = plugin.Short
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: manager.MetadataSubcommandName,
|
||||
Hidden: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(meta)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
12
cli/cobra.go
12
cli/cobra.go
|
@ -12,6 +12,8 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// setupCommonRootCommand contains the setup common to
|
||||
// SetupRootCommand and SetupPluginRootCommand.
|
||||
func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.FlagSet) {
|
||||
opts := cliflags.NewClientOptions()
|
||||
flags := rootCmd.Flags()
|
||||
|
@ -47,6 +49,16 @@ func SetupRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.F
|
|||
return opts, flags
|
||||
}
|
||||
|
||||
// SetupPluginRootCommand sets default usage, help and error handling for a plugin root command.
|
||||
func SetupPluginRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *pflag.FlagSet) {
|
||||
opts, flags := setupCommonRootCommand(rootCmd)
|
||||
|
||||
rootCmd.PersistentFlags().BoolP("help", "", false, "Print usage")
|
||||
rootCmd.PersistentFlags().Lookup("help").Hidden = true
|
||||
|
||||
return opts, flags
|
||||
}
|
||||
|
||||
// FlagErrorFunc prints an error message which matches the format of the
|
||||
// docker/cli/cli error messages
|
||||
func FlagErrorFunc(cmd *cobra.Command, err error) error {
|
||||
|
|
|
@ -56,6 +56,9 @@ binary: build_binary_native_image ## build the CLI
|
|||
|
||||
build: binary ## alias for binary
|
||||
|
||||
plugins: build_binary_native_image ## build the CLI plugin examples
|
||||
docker run --rm $(ENVVARS) $(MOUNTS) $(BINARY_NATIVE_IMAGE_NAME) ./scripts/build/plugins
|
||||
|
||||
.PHONY: clean
|
||||
clean: build_docker_image ## clean build artifacts
|
||||
docker run --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make clean
|
||||
|
@ -76,10 +79,18 @@ cross: build_cross_image ## build the CLI for macOS and Windows
|
|||
binary-windows: build_cross_image ## build the CLI for Windows
|
||||
docker run --rm $(ENVVARS) $(MOUNTS) $(CROSS_IMAGE_NAME) make $@
|
||||
|
||||
.PHONY: plugins-windows
|
||||
plugins-windows: build_cross_image ## build the example CLI plugins for Windows
|
||||
docker run --rm $(ENVVARS) $(MOUNTS) $(CROSS_IMAGE_NAME) make $@
|
||||
|
||||
.PHONY: binary-osx
|
||||
binary-osx: build_cross_image ## build the CLI for macOS
|
||||
docker run --rm $(ENVVARS) $(MOUNTS) $(CROSS_IMAGE_NAME) make $@
|
||||
|
||||
.PHONY: plugins-osx
|
||||
plugins-osx: build_cross_image ## build the example CLI plugins for macOS
|
||||
docker run --rm $(ENVVARS) $(MOUNTS) $(CROSS_IMAGE_NAME) make $@
|
||||
|
||||
.PHONY: dev
|
||||
dev: build_docker_image ## start a build container in interactive mode for in-container development
|
||||
docker run -ti --rm $(ENVVARS) $(MOUNTS) \
|
||||
|
|
|
@ -38,5 +38,6 @@ ARG VERSION
|
|||
ARG GITCOMMIT
|
||||
ENV VERSION=${VERSION} GITCOMMIT=${GITCOMMIT}
|
||||
RUN ./scripts/build/binary
|
||||
RUN ./scripts/build/plugins
|
||||
|
||||
CMD ./scripts/test/e2e/entry
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Build a static binary for the host OS/ARCH
|
||||
#
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
source ./scripts/build/.variables
|
||||
|
||||
mkdir -p "build/plugins-${GOOS}-${GOARCH}"
|
||||
for p in cli-plugins/examples/* ; do
|
||||
[ -d "$p" ] || continue
|
||||
|
||||
n=$(basename "$p")
|
||||
|
||||
TARGET="build/plugins-${GOOS}-${GOARCH}/docker-${n}"
|
||||
|
||||
echo "Building statically linked $TARGET"
|
||||
export CGO_ENABLED=0
|
||||
go build -o "${TARGET}" --ldflags "${LDFLAGS}" "github.com/docker/cli/${p}"
|
||||
done
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Build a static binary for the host OS/ARCH
|
||||
#
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
source ./scripts/build/.variables
|
||||
|
||||
export CGO_ENABLED=1
|
||||
export GOOS=darwin
|
||||
export GOARCH=amd64
|
||||
export CC=o64-clang
|
||||
export CXX=o64-clang++
|
||||
export LDFLAGS="$LDFLAGS -linkmode external -s"
|
||||
export LDFLAGS_STATIC_DOCKER='-extld='${CC}
|
||||
|
||||
source ./scripts/build/plugins
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Build a static binary for the host OS/ARCH
|
||||
#
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
source ./scripts/build/.variables
|
||||
export CC=x86_64-w64-mingw32-gcc
|
||||
export CGO_ENABLED=1
|
||||
export GOOS=windows
|
||||
export GOARCH=amd64
|
||||
|
||||
source ./scripts/build/plugins
|
Loading…
Reference in New Issue