1
0
mirror of https://github.com/docker/cli-docs-tool.git synced 2025-08-08 10:22:04 +03:00

New opts for setting target and plugin capabilities

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2021-10-15 18:04:25 +02:00
parent 27b26b5ec9
commit 67f339f2be
17 changed files with 420 additions and 238 deletions

View File

@@ -9,6 +9,7 @@ on:
jobs: jobs:
update: update:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -

View File

@@ -41,3 +41,20 @@ jobs:
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v2
with: with:
file: ./coverage.txt file: ./coverage.txt
example:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
-
name: Run
run: |
go run main.go
working-directory: ./example

View File

@@ -41,61 +41,10 @@ require (
) )
``` ```
Next, create a file named `docgen.go` inside that directory containing the Next, create a file named `main.go` inside that directory containing the
following Go code: following Go code from [`example/main.go`](example/main.go).
```go Running this example should produce the following output:
package main
import (
"log"
"os"
"path/filepath"
"github.com/docker/buildx/commands"
"github.com/docker/cli/cli/command"
clidocstool "github.com/docker/cli-docs-tool"
"github.com/spf13/cobra"
)
const sourcePath = "docs/"
func main() {
log.SetFlags(0)
dockerCLI, err := command.NewDockerCli()
if err != nil {
log.Printf("ERROR: %+v", err)
}
cmd := &cobra.Command{
Use: "docker [OPTIONS] COMMAND [ARG...]",
Short: "The base command for the Docker CLI.",
DisableAutoGenTag: true,
}
cmd.AddCommand(commands.NewRootCmd("buildx", true, dockerCLI))
clidocstool.DisableFlagsInUseLine(cmd)
cwd, _ := os.Getwd()
source := filepath.Join(cwd, sourcePath)
// Make sure "source" folder is created first
if err = os.MkdirAll(source, 0755); err != nil {
log.Printf("ERROR: %+v", err)
}
// Generate Markdown and YAML documentation to "source" folder
if err = clidocstool.GenTree(cmd, source); err != nil {
log.Printf("ERROR: %+v", err)
}
}
```
Here we create a new instance of Docker CLI with `command.NewDockerCli` and a
subcommand `commands.NewRootCmd` for `buildx`.
Finally, we generate Markdown and YAML documentation with `clidocstool.GenTree`.
```console ```console
$ go run main.go $ go run main.go

View File

@@ -15,38 +15,108 @@
package clidocstool package clidocstool
import ( import (
"io"
"os"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// GenTree creates yaml and markdown structured ref files for this command // Options defines options for cli-docs-tool
// and all descendants in the directory given. This function will just type Options struct {
// call GenMarkdownTree and GenYamlTree functions successively. Root *cobra.Command
func GenTree(cmd *cobra.Command, dir string) error { SourceDir string
TargetDir string
Plugin bool
}
// Client represents an active cli-docs-tool object
type Client struct {
root *cobra.Command
source string
target string
plugin bool
}
// New initializes a new cli-docs-tool client
func New(opts Options) (*Client, error) {
if opts.Root == nil {
return nil, errors.New("root cmd required")
}
if len(opts.SourceDir) == 0 {
return nil, errors.New("source dir required")
}
c := &Client{
root: opts.Root,
source: opts.SourceDir,
plugin: opts.Plugin,
}
if len(opts.TargetDir) == 0 {
c.target = c.source
} else {
c.target = opts.TargetDir
}
if err := os.MkdirAll(c.target, 0755); err != nil {
return nil, err
}
return c, nil
}
// GenAllTree creates all structured ref files for this command and
// all descendants in the directory given.
func (c *Client) GenAllTree() error {
var err error var err error
if err = GenMarkdownTree(cmd, dir); err != nil { if err = c.GenMarkdownTree(c.root); err != nil {
return err return err
} }
if err = GenYamlTree(cmd, dir); err != nil { if err = c.GenYamlTree(c.root); err != nil {
return err return err
} }
return nil return nil
} }
// VisitAll will traverse all commands from the root.
// This is different from the VisitAll of cobra.Command where only parents
// are checked.
func VisitAll(root *cobra.Command, fn func(*cobra.Command)) {
for _, cmd := range root.Commands() {
VisitAll(cmd, fn)
}
fn(root)
}
// DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all // DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
// commands within the tree rooted at cmd. // commands within the tree rooted at cmd.
func DisableFlagsInUseLine(cmd *cobra.Command) { func (c *Client) DisableFlagsInUseLine() {
VisitAll(cmd, func(ccmd *cobra.Command) { visitAll(c.root, func(ccmd *cobra.Command) {
// do not add a `[flags]` to the end of the usage line. // do not add a `[flags]` to the end of the usage line.
ccmd.DisableFlagsInUseLine = true ccmd.DisableFlagsInUseLine = true
}) })
} }
// visitAll traverses all commands from the root.
// This is different from the VisitAll of cobra.Command where only parents
// are checked.
func visitAll(root *cobra.Command, fn func(*cobra.Command)) {
for _, cmd := range root.Commands() {
visitAll(cmd, fn)
}
fn(root)
}
func fileExists(f string) bool {
info, err := os.Stat(f)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func copyFile(src string, dest string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := os.Create(dest)
if err != nil {
return err
}
defer destFile.Close()
if _, err = io.Copy(destFile, srcFile); err != nil {
return err
}
return destFile.Sync()
}

View File

@@ -31,21 +31,25 @@ import (
// GenMarkdownTree will generate a markdown page for this command and all // GenMarkdownTree will generate a markdown page for this command and all
// descendants in the directory given. // descendants in the directory given.
func GenMarkdownTree(cmd *cobra.Command, dir string) error { func (c *Client) GenMarkdownTree(cmd *cobra.Command) error {
for _, c := range cmd.Commands() { for _, sc := range cmd.Commands() {
if err := GenMarkdownTree(c, dir); err != nil { if err := c.GenMarkdownTree(sc); err != nil {
return err return err
} }
} }
if !cmd.HasParent() {
// Skip the root command altogether, to prevent generating a useless
// md file for plugins.
if c.plugin && !cmd.HasParent() {
return nil return nil
} }
log.Printf("INFO: Generating Markdown for %q", cmd.CommandPath()) log.Printf("INFO: Generating Markdown for %q", cmd.CommandPath())
mdFile := mdFilename(cmd) mdFile := mdFilename(cmd)
fullPath := filepath.Join(dir, mdFile) sourcePath := filepath.Join(c.source, mdFile)
targetPath := filepath.Join(c.target, mdFile)
if _, err := os.Stat(fullPath); os.IsNotExist(err) { if !fileExists(sourcePath) {
var icBuf bytes.Buffer var icBuf bytes.Buffer
icTpl, err := template.New("ic").Option("missingkey=error").Parse(`# {{ .Command }} icTpl, err := template.New("ic").Option("missingkey=error").Parse(`# {{ .Command }}
@@ -63,12 +67,14 @@ func GenMarkdownTree(cmd *cobra.Command, dir string) error {
}); err != nil { }); err != nil {
return err return err
} }
if err = ioutil.WriteFile(fullPath, icBuf.Bytes(), 0644); err != nil { if err = ioutil.WriteFile(targetPath, icBuf.Bytes(), 0644); err != nil {
return err return err
} }
} else if err := copyFile(sourcePath, targetPath); err != nil {
return err
} }
content, err := ioutil.ReadFile(fullPath) content, err := ioutil.ReadFile(targetPath)
if err != nil { if err != nil {
return err return err
} }
@@ -91,12 +97,12 @@ func GenMarkdownTree(cmd *cobra.Command, dir string) error {
} }
cont := cs[:start] + "<!---MARKER_GEN_START-->" + "\n" + out + "\n" + cs[end:] cont := cs[:start] + "<!---MARKER_GEN_START-->" + "\n" + out + "\n" + cs[end:]
fi, err := os.Stat(fullPath) fi, err := os.Stat(targetPath)
if err != nil { if err != nil {
return err return err
} }
if err := ioutil.WriteFile(fullPath, []byte(cont), fi.Mode()); err != nil { if err = ioutil.WriteFile(targetPath, []byte(cont), fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to write %s", fullPath) return errors.Wrapf(err, "failed to write %s", targetPath)
} }
return nil return nil

View File

@@ -17,67 +17,39 @@ package clidocstool
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
//nolint:errcheck //nolint:errcheck
func TestGenMarkdownTree(t *testing.T) { func TestGenMarkdownTree(t *testing.T) {
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
s := &cobra.Command{Use: "sub [OPTIONS] arg1 arg2"}
flags := s.Flags()
_ = flags.Bool("push", false, "Shorthand for --output=type=registry")
_ = flags.Bool("load", false, "Shorthand for --output=type=docker")
_ = flags.StringArrayP("tag", "t", []string{}, "Name and optionally a tag in the 'name:tag' format")
flags.SetAnnotation("tag", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t"})
_ = flags.StringArray("build-arg", []string{}, "Set build-time variables")
flags.SetAnnotation("build-arg", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg"})
_ = flags.StringP("file", "f", "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
flags.SetAnnotation("file", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f"})
_ = flags.StringArray("label", []string{}, "Set metadata for an image")
_ = flags.StringArray("cache-from", []string{}, "External cache sources (eg. user/app:cache, type=local,src=path/to/dir)")
_ = flags.StringArray("cache-to", []string{}, "Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)")
_ = flags.String("target", "", "Set the target build stage to build.")
flags.SetAnnotation("target", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target"})
_ = flags.StringSlice("allow", []string{}, "Allow extra privileged entitlement, e.g. network.host, security.insecure")
_ = flags.StringArray("platform", []string{}, "Set target platform for build")
_ = flags.StringArray("secret", []string{}, "Secret file to expose to the build: id=mysecret,src=/local/secret")
_ = flags.StringArray("ssh", []string{}, "SSH agent socket or keys to expose to the build (format: `default|<id>[=<socket>|<key>[,<key>]]`)")
_ = flags.StringArrayP("output", "o", []string{}, "Output destination (format: type=local,dest=path)")
// not implemented
_ = flags.String("network", "default", "Set the networking mode for the RUN instructions during build")
_ = flags.StringSlice("add-host", []string{}, "Add a custom host-to-IP mapping (host:ip)")
_ = flags.SetAnnotation("add-host", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host"})
_ = flags.String("iidfile", "", "Write the image ID to the file")
// hidden flags
_ = flags.BoolP("quiet", "q", false, "Suppress the build output and print image ID on success")
flags.MarkHidden("quiet")
_ = flags.Bool("squash", false, "Squash newly built layers into a single new layer")
flags.MarkHidden("squash")
_ = flags.String("ulimit", "", "Ulimit options")
flags.MarkHidden("ulimit")
_ = flags.StringSlice("security-opt", []string{}, "Security options")
flags.MarkHidden("security-opt")
_ = flags.Bool("compress", false, "Compress the build context using gzip")
c.AddCommand(s)
tmpdir, err := ioutil.TempDir("", "test-gen-markdown-tree") tmpdir, err := ioutil.TempDir("", "test-gen-markdown-tree")
require.NoError(t, err) require.NoError(t, err)
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
require.NoError(t, GenMarkdownTree(c, tmpdir))
fres := filepath.Join(tmpdir, "sub.md") c, err := New(Options{
require.FileExists(t, fres) Root: buildxCmd,
bres, err := ioutil.ReadFile(fres) SourceDir: tmpdir,
Plugin: true,
})
require.NoError(t, err) require.NoError(t, err)
bexc, err := ioutil.ReadFile("fixtures/sub.md") require.NoError(t, c.GenMarkdownTree(buildxCmd))
require.NoError(t, err)
assert.Equal(t, string(bres), string(bexc)) for _, tt := range []string{"buildx.md", "buildx_build.md"} {
tt := tt
t.Run(tt, func(t *testing.T) {
fres := filepath.Join(tmpdir, tt)
require.FileExists(t, fres)
bres, err := ioutil.ReadFile(fres)
require.NoError(t, err)
bexc, err := ioutil.ReadFile(path.Join("fixtures", tt))
require.NoError(t, err)
assert.Equal(t, string(bexc), string(bres))
})
}
} }

124
clidocstool_test.go Normal file
View File

@@ -0,0 +1,124 @@
// Copyright 2021 cli-docs-tool authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package clidocstool
import (
"io/ioutil"
"os"
"path"
"path/filepath"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
dockerCmd *cobra.Command
buildxCmd *cobra.Command
buildxBuildCmd *cobra.Command
)
//nolint:errcheck
func init() {
dockerCmd = &cobra.Command{
Use: "docker [OPTIONS] COMMAND [ARG...]",
Short: "A self-sufficient runtime for containers",
SilenceUsage: true,
SilenceErrors: true,
TraverseChildren: true,
Run: func(cmd *cobra.Command, args []string) {},
Version: "20.10.8",
DisableFlagsInUseLine: true,
}
buildxCmd = &cobra.Command{
Use: "buildx",
Short: "Build with BuildKit",
}
buildxBuildCmd = &cobra.Command{
Use: "build [OPTIONS] PATH | URL | -",
Aliases: []string{"b"},
Short: "Start a build",
Run: func(cmd *cobra.Command, args []string) {},
}
flags := buildxBuildCmd.Flags()
flags.Bool("push", false, "Shorthand for --output=type=registry")
flags.Bool("load", false, "Shorthand for --output=type=docker")
flags.StringArrayP("tag", "t", []string{}, "Name and optionally a tag in the 'name:tag' format")
flags.SetAnnotation("tag", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t"})
flags.StringArray("build-arg", []string{}, "Set build-time variables")
flags.SetAnnotation("build-arg", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg"})
flags.StringP("file", "f", "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
flags.SetAnnotation("file", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f"})
flags.StringArray("label", []string{}, "Set metadata for an image")
flags.StringArray("cache-from", []string{}, "External cache sources (eg. user/app:cache, type=local,src=path/to/dir)")
flags.StringArray("cache-to", []string{}, "Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)")
flags.String("target", "", "Set the target build stage to build.")
flags.SetAnnotation("target", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target"})
flags.StringSlice("allow", []string{}, "Allow extra privileged entitlement, e.g. network.host, security.insecure")
flags.StringArray("platform", []string{}, "Set target platform for build")
flags.StringArray("secret", []string{}, "Secret file to expose to the build: id=mysecret,src=/local/secret")
flags.StringArray("ssh", []string{}, "SSH agent socket or keys to expose to the build (format: `default|<id>[=<socket>|<key>[,<key>]]`)")
flags.StringArrayP("output", "o", []string{}, "Output destination (format: type=local,dest=path)")
// not implemented
flags.String("network", "default", "Set the networking mode for the RUN instructions during build")
flags.StringSlice("add-host", []string{}, "Add a custom host-to-IP mapping (host:ip)")
flags.SetAnnotation("add-host", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host"})
flags.String("iidfile", "", "Write the image ID to the file")
// hidden flags
flags.BoolP("quiet", "q", false, "Suppress the build output and print image ID on success")
flags.MarkHidden("quiet")
flags.Bool("squash", false, "Squash newly built layers into a single new layer")
flags.MarkHidden("squash")
flags.String("ulimit", "", "Ulimit options")
flags.MarkHidden("ulimit")
flags.StringSlice("security-opt", []string{}, "Security options")
flags.MarkHidden("security-opt")
flags.Bool("compress", false, "Compress the build context using gzip")
buildxCmd.AddCommand(buildxBuildCmd)
dockerCmd.AddCommand(buildxCmd)
}
//nolint:errcheck
func TestGenAllTree(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "test-gen-all-tree")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
c, err := New(Options{
Root: buildxCmd,
SourceDir: tmpdir,
Plugin: true,
})
require.NoError(t, err)
require.NoError(t, c.GenAllTree())
for _, tt := range []string{"buildx.md", "buildx_build.md", "docker_buildx.yaml", "docker_buildx_build.yaml"} {
tt := tt
t.Run(tt, func(t *testing.T) {
fres := filepath.Join(tmpdir, tt)
require.FileExists(t, fres)
bres, err := ioutil.ReadFile(fres)
require.NoError(t, err)
bexc, err := ioutil.ReadFile(path.Join("fixtures", tt))
require.NoError(t, err)
assert.Equal(t, string(bexc), string(bres))
})
}
}

View File

@@ -74,62 +74,58 @@ type cmdDoc struct {
// correctly if your command names have `-` in them. If you have `cmd` with two // correctly if your command names have `-` in them. If you have `cmd` with two
// subcmds, `sub` and `sub-third`, and `sub` has a subcommand called `third` // subcmds, `sub` and `sub-third`, and `sub` has a subcommand called `third`
// it is undefined which help output will be in the file `cmd-sub-third.1`. // it is undefined which help output will be in the file `cmd-sub-third.1`.
func GenYamlTree(cmd *cobra.Command, dir string) error { func (c *Client) GenYamlTree(cmd *cobra.Command) error {
emptyStr := func(s string) string { return "" } emptyStr := func(s string) string { return "" }
if err := loadLongDescription(cmd, dir); err != nil { if err := c.loadLongDescription(cmd); err != nil {
return err return err
} }
return GenYamlTreeCustom(cmd, dir, emptyStr) return c.genYamlTreeCustom(cmd, emptyStr)
} }
// GenYamlTreeCustom creates yaml structured ref files. // genYamlTreeCustom creates yaml structured ref files.
func GenYamlTreeCustom(cmd *cobra.Command, dir string, filePrepender func(string) string) error { func (c *Client) genYamlTreeCustom(cmd *cobra.Command, filePrepender func(string) string) error {
for _, c := range cmd.Commands() { for _, sc := range cmd.Commands() {
if !c.Runnable() && !c.HasAvailableSubCommands() { if !sc.Runnable() && !sc.HasAvailableSubCommands() {
// skip non-runnable commands without subcommands // skip non-runnable commands without subcommands
// but *do* generate YAML for hidden and deprecated commands // but *do* generate YAML for hidden and deprecated commands
// the YAML will have those included as metadata, so that the // the YAML will have those included as metadata, so that the
// documentation repository can decide whether or not to present them // documentation repository can decide whether or not to present them
continue continue
} }
if err := GenYamlTreeCustom(c, dir, filePrepender); err != nil { if err := c.genYamlTreeCustom(sc, filePrepender); err != nil {
return err return err
} }
} }
// TODO: conditionally skip the root command (for plugins)
//
// The "root" command used in the generator is just a "stub", and only has a // The "root" command used in the generator is just a "stub", and only has a
// list of subcommands, but not (e.g.) global options/flags. We should fix // list of subcommands, but not (e.g.) global options/flags. We should fix
// that, so that the YAML file for the docker "root" command contains the // that, so that the YAML file for the docker "root" command contains the
// global flags. // global flags.
//
// If we're using this code to generate YAML docs for a plugin, the root- // Skip the root command altogether, to prevent generating a useless
// command is even less useful; in that case, the root command represents // YAML file for plugins.
// the "docker" command, and is a "dummy" with no flags, and only a single if c.plugin && !cmd.HasParent() {
// subcommand (the plugin's top command). For plugins, we should skip the
// root command altogether, to prevent generating a useless YAML file.
if !cmd.HasParent() {
return nil return nil
} }
log.Printf("INFO: Generating YAML for %q", cmd.CommandPath()) log.Printf("INFO: Generating YAML for %q", cmd.CommandPath())
basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".yaml" basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".yaml"
filename := filepath.Join(dir, basename) target := filepath.Join(c.target, basename)
f, err := os.Create(filename) f, err := os.Create(target)
if err != nil { if err != nil {
return err return err
} }
defer f.Close() defer f.Close()
if _, err := io.WriteString(f, filePrepender(filename)); err != nil { if _, err := io.WriteString(f, filePrepender(target)); err != nil {
return err return err
} }
return GenYamlCustom(cmd, f) return c.genYamlCustom(cmd, f)
} }
// GenYamlCustom creates custom yaml output. // genYamlCustom creates custom yaml output.
// nolint: gocyclo // nolint: gocyclo
func GenYamlCustom(cmd *cobra.Command, w io.Writer) error { func (c *Client) genYamlCustom(cmd *cobra.Command, w io.Writer) error {
const ( const (
// shortMaxWidth is the maximum width for the "Short" description before // shortMaxWidth is the maximum width for the "Short" description before
// we force YAML to use multi-line syntax. The goal is to make the total // we force YAML to use multi-line syntax. The goal is to make the total
@@ -338,15 +334,15 @@ func hasSeeAlso(cmd *cobra.Command) bool {
} }
// loadLongDescription gets long descriptions and examples from markdown. // loadLongDescription gets long descriptions and examples from markdown.
func loadLongDescription(parentCmd *cobra.Command, path string) error { func (c *Client) loadLongDescription(parentCmd *cobra.Command) error {
for _, cmd := range parentCmd.Commands() { for _, cmd := range parentCmd.Commands() {
if cmd.HasSubCommands() { if cmd.HasSubCommands() {
if err := loadLongDescription(cmd, path); err != nil { if err := c.loadLongDescription(cmd); err != nil {
return err return err
} }
} }
name := cmd.CommandPath() name := cmd.CommandPath()
if i := strings.Index(name, " "); i >= 0 { if i := strings.Index(name, " "); c.plugin && i >= 0 {
// remove root command / binary name // remove root command / binary name
name = name[i+1:] name = name[i+1:]
} }
@@ -354,8 +350,8 @@ func loadLongDescription(parentCmd *cobra.Command, path string) error {
continue continue
} }
mdFile := strings.ReplaceAll(name, " ", "_") + ".md" mdFile := strings.ReplaceAll(name, " ", "_") + ".md"
fullPath := filepath.Join(path, mdFile) sourcePath := filepath.Join(c.source, mdFile)
content, err := ioutil.ReadFile(fullPath) content, err := ioutil.ReadFile(sourcePath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
log.Printf("WARN: %s does not exist, skipping Markdown examples for YAML doc\n", mdFile) log.Printf("WARN: %s does not exist, skipping Markdown examples for YAML doc\n", mdFile)
continue continue

View File

@@ -17,67 +17,39 @@ package clidocstool
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
//nolint:errcheck //nolint:errcheck
func TestGenYamlTree(t *testing.T) { func TestGenYamlTree(t *testing.T) {
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
s := &cobra.Command{Use: "sub [OPTIONS] arg1 arg2", Run: func(cmd *cobra.Command, args []string) {}}
flags := s.Flags()
_ = flags.Bool("push", false, "Shorthand for --output=type=registry")
_ = flags.Bool("load", false, "Shorthand for --output=type=docker")
_ = flags.StringArrayP("tag", "t", []string{}, "Name and optionally a tag in the 'name:tag' format")
flags.SetAnnotation("tag", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t"})
_ = flags.StringArray("build-arg", []string{}, "Set build-time variables")
flags.SetAnnotation("build-arg", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg"})
_ = flags.StringP("file", "f", "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
flags.SetAnnotation("file", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f"})
_ = flags.StringArray("label", []string{}, "Set metadata for an image")
_ = flags.StringArray("cache-from", []string{}, "External cache sources (eg. user/app:cache, type=local,src=path/to/dir)")
_ = flags.StringArray("cache-to", []string{}, "Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)")
_ = flags.String("target", "", "Set the target build stage to build.")
flags.SetAnnotation("target", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target"})
_ = flags.StringSlice("allow", []string{}, "Allow extra privileged entitlement, e.g. network.host, security.insecure")
_ = flags.StringArray("platform", []string{}, "Set target platform for build")
_ = flags.StringArray("secret", []string{}, "Secret file to expose to the build: id=mysecret,src=/local/secret")
_ = flags.StringArray("ssh", []string{}, "SSH agent socket or keys to expose to the build (format: `default|<id>[=<socket>|<key>[,<key>]]`)")
_ = flags.StringArrayP("output", "o", []string{}, "Output destination (format: type=local,dest=path)")
// not implemented
_ = flags.String("network", "default", "Set the networking mode for the RUN instructions during build")
_ = flags.StringSlice("add-host", []string{}, "Add a custom host-to-IP mapping (host:ip)")
_ = flags.SetAnnotation("add-host", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host"})
_ = flags.String("iidfile", "", "Write the image ID to the file")
// hidden flags
_ = flags.BoolP("quiet", "q", false, "Suppress the build output and print image ID on success")
flags.MarkHidden("quiet")
_ = flags.Bool("squash", false, "Squash newly built layers into a single new layer")
flags.MarkHidden("squash")
_ = flags.String("ulimit", "", "Ulimit options")
flags.MarkHidden("ulimit")
_ = flags.StringSlice("security-opt", []string{}, "Security options")
flags.MarkHidden("security-opt")
_ = flags.Bool("compress", false, "Compress the build context using gzip")
c.AddCommand(s)
tmpdir, err := ioutil.TempDir("", "test-gen-yaml-tree") tmpdir, err := ioutil.TempDir("", "test-gen-yaml-tree")
require.NoError(t, err) require.NoError(t, err)
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
require.NoError(t, GenYamlTree(c, tmpdir))
fres := filepath.Join(tmpdir, "do_sub.yaml") c, err := New(Options{
require.FileExists(t, fres) Root: buildxCmd,
bres, err := ioutil.ReadFile(fres) SourceDir: tmpdir,
Plugin: true,
})
require.NoError(t, err) require.NoError(t, err)
bexc, err := ioutil.ReadFile("fixtures/do_sub.yaml") require.NoError(t, c.GenYamlTree(buildxCmd))
require.NoError(t, err)
assert.Equal(t, string(bres), string(bexc)) for _, tt := range []string{"docker_buildx.yaml", "docker_buildx_build.yaml"} {
tt := tt
t.Run(tt, func(t *testing.T) {
fres := filepath.Join(tmpdir, tt)
require.FileExists(t, fres)
bres, err := ioutil.ReadFile(fres)
require.NoError(t, err)
bexc, err := ioutil.ReadFile(path.Join("fixtures", tt))
require.NoError(t, err)
assert.Equal(t, string(bexc), string(bres))
})
}
} }

View File

@@ -1,6 +1,6 @@
# Example # Example
The following example will generate YAML and Markdown docs for The following example will generate all supported docs for
[Docker buildx](https://github.com/docker/buildx) CLI. [Docker buildx](https://github.com/docker/buildx) CLI.
```console ```console

View File

@@ -3,14 +3,15 @@ module github.com/docker/cli-docs-tool/example
go 1.16 go 1.16
require ( require (
github.com/docker/buildx v0.6.0 github.com/docker/buildx v0.6.3
github.com/docker/cli v20.10.7+incompatible github.com/docker/cli v20.10.7+incompatible
github.com/docker/cli-docs-tool v0.0.0 github.com/docker/cli-docs-tool v0.0.0
github.com/spf13/cobra v1.2.1 github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
) )
replace ( replace (
github.com/docker/cli => github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible github.com/docker/cli => github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20210609100121-ef4d47340142+incompatible
github.com/docker/cli-docs-tool => ../ github.com/docker/cli-docs-tool => ../
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20210609100121-ef4d47340142+incompatible
) )

View File

@@ -69,8 +69,9 @@ github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEY
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=
github.com/Microsoft/hcsshim v0.8.16 h1:8/auA4LFIZFTGrqfKhGBSXwM6/4X1fHa/xniyEHu8ac=
github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600=
github.com/Microsoft/hcsshim v0.8.18 h1:cYnKADiM1869gvBpos3YCteeT6sZLB48lB5dmMMs8Tg=
github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=
github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
@@ -151,8 +152,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/compose-spec/compose-go v0.0.0-20210706130854-69459d4976b5 h1:PpI72CT1bcVPNZyqI1HI8UhQVRVtqLb2tdwi5WphN3c= github.com/compose-spec/compose-go v0.0.0-20210729195839-de56f4f0cb3c h1:lSR4wokZlq+Q8uJpgZuFMs3VoLaYVV07cJOZHa1zRBg=
github.com/compose-spec/compose-go v0.0.0-20210706130854-69459d4976b5/go.mod h1:5V65rPnTvvQagtoMxTneJ2QicLq6ZRQQ7fOgPN226fo= github.com/compose-spec/compose-go v0.0.0-20210729195839-de56f4f0cb3c/go.mod h1:5V65rPnTvvQagtoMxTneJ2QicLq6ZRQQ7fOgPN226fo=
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
@@ -186,8 +187,10 @@ github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7
github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=
github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI=
github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
github.com/containerd/containerd v1.5.2 h1:MG/Bg1pbmMb61j3wHCFWPxESXHieiKr2xG64px/k8zQ= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
github.com/containerd/containerd v1.5.4 h1:uPF0og3ByFzDnaStfiQj3fVGTEtaSNyU+bW7GR/nqGA=
github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
@@ -282,8 +285,8 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e h1:n81KvOMrLZa+VWHwST7dun9f0G98X3zREHS1ztYzZKU= github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e h1:n81KvOMrLZa+VWHwST7dun9f0G98X3zREHS1ztYzZKU=
github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mod h1:xpWTC2KnJMiDLkoawhsPQcXjvwATEBcbq0xevG2YR9M= github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mod h1:xpWTC2KnJMiDLkoawhsPQcXjvwATEBcbq0xevG2YR9M=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/buildx v0.6.0 h1:UuJvtuK8SQX/7Y9i1fJR3Jq6kXBSFcSJtudlALu9iV8= github.com/docker/buildx v0.6.3 h1:/PReewxhHiBdNQmg6E9lUa56wKba0oXRBrKhwA90JXE=
github.com/docker/buildx v0.6.0/go.mod h1:ue0mOStExTp8vSb1wrvbpL9VoL0e66EK9etSCCIhuFM= github.com/docker/buildx v0.6.3/go.mod h1:7gkFFXWFWo+vfXMNijFRBO0KwEDITP10TUWty5ZJNz0=
github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible h1:CaaxCD/l9Dxogu6lxf7AQautlv3sHULrasPadayp0fM= github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible h1:CaaxCD/l9Dxogu6lxf7AQautlv3sHULrasPadayp0fM=
github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496 h1:90ytrX1dbzL7Uf/hHiuWwvywC+gikHv4hkAy4CwRTbs= github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496 h1:90ytrX1dbzL7Uf/hHiuWwvywC+gikHv4hkAy4CwRTbs=

View File

@@ -17,40 +17,72 @@ package main
import ( import (
"log" "log"
"os" "os"
"path/filepath"
"github.com/docker/buildx/commands" "github.com/docker/buildx/commands"
clidocstool "github.com/docker/cli-docs-tool" clidocstool "github.com/docker/cli-docs-tool"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
) )
const sourcePath = "docs/" const (
pluginName = "buildx"
defaultSourcePath = "docs/"
)
func main() { type options struct {
source string
target string
}
func gen(opts *options) error {
log.SetFlags(0) log.SetFlags(0)
// create a new instance of Docker CLI
dockerCLI, err := command.NewDockerCli() dockerCLI, err := command.NewDockerCli()
if err != nil { if err != nil {
log.Printf("ERROR: %+v", err) return err
} }
// root command
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "docker [OPTIONS] COMMAND [ARG...]", Use: "buildx",
Short: "The base command for the Docker CLI.", Short: "Build with BuildKit",
DisableAutoGenTag: true,
} }
cmd.AddCommand(commands.NewRootCmd("buildx", true, dockerCLI)) // subcommand for the plugin
clidocstool.DisableFlagsInUseLine(cmd) cmd.AddCommand(commands.NewRootCmd(pluginName, true, dockerCLI))
cwd, _ := os.Getwd() // create a new instance of cli-docs-tool
source := filepath.Join(cwd, sourcePath) c, err := clidocstool.New(clidocstool.Options{
Root: cmd,
if err = os.MkdirAll(source, 0755); err != nil { SourceDir: opts.source,
log.Printf("ERROR: %+v", err) TargetDir: opts.target,
Plugin: true,
})
if err != nil {
return err
} }
if err = clidocstool.GenTree(cmd, source); err != nil { c.DisableFlagsInUseLine()
// generate all supported docs formats
return c.GenAllTree()
}
func run() error {
opts := &options{}
flags := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError)
flags.StringVar(&opts.source, "source", defaultSourcePath, "Docs source folder")
flags.StringVar(&opts.target, "target", defaultSourcePath, "Docs target folder")
if err := flags.Parse(os.Args[1:]); err != nil {
return err
}
return gen(opts)
}
func main() {
if err := run(); err != nil {
log.Printf("ERROR: %+v", err) log.Printf("ERROR: %+v", err)
os.Exit(1)
} }
} }

15
fixtures/buildx.md Normal file
View File

@@ -0,0 +1,15 @@
# docker buildx
<!---MARKER_GEN_START-->
Build with BuildKit
### Subcommands
| Name | Description |
| --- | --- |
| [`build`](buildx_build.md) | Start a build |
<!---MARKER_GEN_END-->

View File

@@ -1,6 +1,12 @@
# do sub # docker buildx build
<!---MARKER_GEN_START--> <!---MARKER_GEN_START-->
Start a build
### Aliases
`build`, `b`
### Options ### Options
| Name | Description | | Name | Description |

View File

@@ -0,0 +1,15 @@
command: docker buildx
short: Build with BuildKit
long: Build with BuildKit
pname: docker
plink: docker.yaml
cname:
- docker buildx build
clink:
- docker_buildx_build.yaml
deprecated: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false

View File

@@ -1,7 +1,10 @@
command: do sub command: docker buildx build
usage: do sub [OPTIONS] arg1 arg2 [flags] aliases: b
pname: do short: Start a build
plink: do.yaml long: Start a build
usage: docker buildx build [OPTIONS] PATH | URL | - [flags]
pname: docker buildx
plink: docker_buildx.yaml
options: options:
- option: add-host - option: add-host
value_type: stringSlice value_type: stringSlice