Signed-off-by: French Ben <frenchben@docker.com>
Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit 5443f0152f77eb983e6475474b02c2e11d975f1c)
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
| ... | ... |
@@ -141,6 +141,9 @@ run: build ## run the docker daemon in a container |
| 141 | 141 |
shell: build ## start a shell inside the build env |
| 142 | 142 |
$(DOCKER_RUN_DOCKER) bash |
| 143 | 143 |
|
| 144 |
+yaml-docs-gen: build ## generate documentation YAML files consumed by docs repo |
|
| 145 |
+ $(DOCKER_RUN_DOCKER) sh -c 'hack/make.sh yaml-docs-generator && ( cd bundles/latest/yaml-docs-generator; mkdir docs; ./yaml-docs-generator --target $$(pwd)/docs )' |
|
| 146 |
+ |
|
| 144 | 147 |
test: build ## run the unit, integration and docker-py tests |
| 145 | 148 |
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary cross test-unit test-integration-cli test-docker-py |
| 146 | 149 |
|
| ... | ... |
@@ -26,7 +26,7 @@ Options: |
| 26 | 26 |
The tarball may be compressed with gzip, bzip, or xz |
| 27 | 27 |
-q, --quiet Suppress the load output but still outputs the imported images |
| 28 | 28 |
``` |
| 29 |
-## Descriptino |
|
| 29 |
+## Description |
|
| 30 | 30 |
|
| 31 | 31 |
`docker load` loads a tarred repository from a file or the standard input stream. |
| 32 | 32 |
It restores both images and tags. |
| 0 | 4 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,86 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "log" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path/filepath" |
|
| 8 |
+ "strings" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/docker/cli/command" |
|
| 11 |
+ "github.com/docker/docker/cli/command/commands" |
|
| 12 |
+ "github.com/docker/docker/pkg/term" |
|
| 13 |
+ "github.com/spf13/cobra" |
|
| 14 |
+ "github.com/spf13/pflag" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+const descriptionSourcePath = "docs/reference/commandline/" |
|
| 18 |
+ |
|
| 19 |
+func generateCliYaml(opts *options) error {
|
|
| 20 |
+ stdin, stdout, stderr := term.StdStreams() |
|
| 21 |
+ dockerCli := command.NewDockerCli(stdin, stdout, stderr) |
|
| 22 |
+ cmd := &cobra.Command{Use: "docker"}
|
|
| 23 |
+ commands.AddCommands(cmd, dockerCli) |
|
| 24 |
+ source := filepath.Join(opts.source, descriptionSourcePath) |
|
| 25 |
+ if err := loadLongDescription(cmd, source); err != nil {
|
|
| 26 |
+ return err |
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ cmd.DisableAutoGenTag = true |
|
| 30 |
+ return GenYamlTree(cmd, opts.target) |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+func loadLongDescription(cmd *cobra.Command, path ...string) error {
|
|
| 34 |
+ for _, cmd := range cmd.Commands() {
|
|
| 35 |
+ if cmd.Name() == "" {
|
|
| 36 |
+ continue |
|
| 37 |
+ } |
|
| 38 |
+ fullpath := filepath.Join(path[0], strings.Join(append(path[1:], cmd.Name()), "_")+".md") |
|
| 39 |
+ |
|
| 40 |
+ if cmd.HasSubCommands() {
|
|
| 41 |
+ loadLongDescription(cmd, path[0], cmd.Name()) |
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ if _, err := os.Stat(fullpath); err != nil {
|
|
| 45 |
+ log.Printf("WARN: %s does not exist, skipping\n", fullpath)
|
|
| 46 |
+ continue |
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 49 |
+ content, err := ioutil.ReadFile(fullpath) |
|
| 50 |
+ if err != nil {
|
|
| 51 |
+ return err |
|
| 52 |
+ } |
|
| 53 |
+ description, examples := parseMDContent(string(content)) |
|
| 54 |
+ cmd.Long = description |
|
| 55 |
+ cmd.Example = examples |
|
| 56 |
+ } |
|
| 57 |
+ return nil |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 60 |
+type options struct {
|
|
| 61 |
+ source string |
|
| 62 |
+ target string |
|
| 63 |
+} |
|
| 64 |
+ |
|
| 65 |
+func parseArgs() (*options, error) {
|
|
| 66 |
+ opts := &options{}
|
|
| 67 |
+ cwd, _ := os.Getwd() |
|
| 68 |
+ flags := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError) |
|
| 69 |
+ flags.StringVar(&opts.source, "root", cwd, "Path to project root") |
|
| 70 |
+ flags.StringVar(&opts.target, "target", "/tmp", "Target path for generated yaml files") |
|
| 71 |
+ err := flags.Parse(os.Args[1:]) |
|
| 72 |
+ return opts, err |
|
| 73 |
+} |
|
| 74 |
+ |
|
| 75 |
+func main() {
|
|
| 76 |
+ opts, err := parseArgs() |
|
| 77 |
+ if err != nil {
|
|
| 78 |
+ fmt.Fprintln(os.Stderr, err.Error()) |
|
| 79 |
+ } |
|
| 80 |
+ fmt.Printf("Project root: %s\n", opts.source)
|
|
| 81 |
+ fmt.Printf("Generating yaml files into %s\n", opts.target)
|
|
| 82 |
+ if err := generateCliYaml(opts); err != nil {
|
|
| 83 |
+ fmt.Fprintf(os.Stderr, "Failed to generate yaml files: %s\n", err.Error()) |
|
| 84 |
+ } |
|
| 85 |
+} |
| 0 | 86 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,212 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "os" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ "sort" |
|
| 8 |
+ "strings" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/spf13/cobra" |
|
| 11 |
+ "github.com/spf13/pflag" |
|
| 12 |
+ "gopkg.in/yaml.v2" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+type cmdOption struct {
|
|
| 16 |
+ Option string |
|
| 17 |
+ Shorthand string `yaml:",omitempty"` |
|
| 18 |
+ DefaultValue string `yaml:"default_value,omitempty"` |
|
| 19 |
+ Description string `yaml:",omitempty"` |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+type cmdDoc struct {
|
|
| 23 |
+ Name string `yaml:"command"` |
|
| 24 |
+ SeeAlso []string `yaml:"parent,omitempty"` |
|
| 25 |
+ Version string `yaml:"engine_version,omitempty"` |
|
| 26 |
+ Aliases string `yaml:",omitempty"` |
|
| 27 |
+ Short string `yaml:",omitempty"` |
|
| 28 |
+ Long string `yaml:",omitempty"` |
|
| 29 |
+ Usage string `yaml:",omitempty"` |
|
| 30 |
+ Pname string `yaml:",omitempty"` |
|
| 31 |
+ Plink string `yaml:",omitempty"` |
|
| 32 |
+ Cname []string `yaml:",omitempty"` |
|
| 33 |
+ Clink []string `yaml:",omitempty"` |
|
| 34 |
+ Options []cmdOption `yaml:",omitempty"` |
|
| 35 |
+ InheritedOptions []cmdOption `yaml:"inherited_options,omitempty"` |
|
| 36 |
+ Example string `yaml:"examples,omitempty"` |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+// GenYamlTree creates yaml structured ref files |
|
| 40 |
+func GenYamlTree(cmd *cobra.Command, dir string) error {
|
|
| 41 |
+ identity := func(s string) string { return s }
|
|
| 42 |
+ emptyStr := func(s string) string { return "" }
|
|
| 43 |
+ return GenYamlTreeCustom(cmd, dir, emptyStr, identity) |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+// GenYamlTreeCustom creates yaml structured ref files |
|
| 47 |
+func GenYamlTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error {
|
|
| 48 |
+ for _, c := range cmd.Commands() {
|
|
| 49 |
+ if !c.IsAvailableCommand() || c.IsHelpCommand() {
|
|
| 50 |
+ continue |
|
| 51 |
+ } |
|
| 52 |
+ if err := GenYamlTreeCustom(c, dir, filePrepender, linkHandler); err != nil {
|
|
| 53 |
+ return err |
|
| 54 |
+ } |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".yaml" |
|
| 58 |
+ filename := filepath.Join(dir, basename) |
|
| 59 |
+ f, err := os.Create(filename) |
|
| 60 |
+ if err != nil {
|
|
| 61 |
+ return err |
|
| 62 |
+ } |
|
| 63 |
+ defer f.Close() |
|
| 64 |
+ |
|
| 65 |
+ if _, err := io.WriteString(f, filePrepender(filename)); err != nil {
|
|
| 66 |
+ return err |
|
| 67 |
+ } |
|
| 68 |
+ if err := GenYamlCustom(cmd, f, linkHandler); err != nil {
|
|
| 69 |
+ return err |
|
| 70 |
+ } |
|
| 71 |
+ return nil |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+// GenYamlCustom creates custom yaml output |
|
| 75 |
+func GenYamlCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error {
|
|
| 76 |
+ cliDoc := cmdDoc{}
|
|
| 77 |
+ cliDoc.Name = cmd.CommandPath() |
|
| 78 |
+ |
|
| 79 |
+ // Check experimental: ok := cmd.Tags["experimental"] |
|
| 80 |
+ |
|
| 81 |
+ cliDoc.Aliases = strings.Join(cmd.Aliases, ", ") |
|
| 82 |
+ cliDoc.Short = cmd.Short |
|
| 83 |
+ cliDoc.Long = cmd.Long |
|
| 84 |
+ if len(cliDoc.Long) == 0 {
|
|
| 85 |
+ cliDoc.Long = cliDoc.Short |
|
| 86 |
+ } |
|
| 87 |
+ |
|
| 88 |
+ if cmd.Runnable() {
|
|
| 89 |
+ cliDoc.Usage = cmd.UseLine() |
|
| 90 |
+ } |
|
| 91 |
+ |
|
| 92 |
+ if len(cmd.Example) > 0 {
|
|
| 93 |
+ cliDoc.Example = cmd.Example |
|
| 94 |
+ } |
|
| 95 |
+ |
|
| 96 |
+ flags := cmd.NonInheritedFlags() |
|
| 97 |
+ if flags.HasFlags() {
|
|
| 98 |
+ cliDoc.Options = genFlagResult(flags) |
|
| 99 |
+ } |
|
| 100 |
+ flags = cmd.InheritedFlags() |
|
| 101 |
+ if flags.HasFlags() {
|
|
| 102 |
+ cliDoc.InheritedOptions = genFlagResult(flags) |
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ if hasSeeAlso(cmd) {
|
|
| 106 |
+ if cmd.HasParent() {
|
|
| 107 |
+ parent := cmd.Parent() |
|
| 108 |
+ cliDoc.Pname = parent.CommandPath() |
|
| 109 |
+ link := cliDoc.Pname + ".yaml" |
|
| 110 |
+ cliDoc.Plink = strings.Replace(link, " ", "_", -1) |
|
| 111 |
+ cmd.VisitParents(func(c *cobra.Command) {
|
|
| 112 |
+ if c.DisableAutoGenTag {
|
|
| 113 |
+ cmd.DisableAutoGenTag = c.DisableAutoGenTag |
|
| 114 |
+ } |
|
| 115 |
+ }) |
|
| 116 |
+ } |
|
| 117 |
+ |
|
| 118 |
+ children := cmd.Commands() |
|
| 119 |
+ sort.Sort(byName(children)) |
|
| 120 |
+ |
|
| 121 |
+ for _, child := range children {
|
|
| 122 |
+ if !child.IsAvailableCommand() || child.IsHelpCommand() {
|
|
| 123 |
+ continue |
|
| 124 |
+ } |
|
| 125 |
+ currentChild := cliDoc.Name + " " + child.Name() |
|
| 126 |
+ cliDoc.Cname = append(cliDoc.Cname, cliDoc.Name+" "+child.Name()) |
|
| 127 |
+ link := currentChild + ".yaml" |
|
| 128 |
+ cliDoc.Clink = append(cliDoc.Clink, strings.Replace(link, " ", "_", -1)) |
|
| 129 |
+ } |
|
| 130 |
+ } |
|
| 131 |
+ |
|
| 132 |
+ final, err := yaml.Marshal(&cliDoc) |
|
| 133 |
+ if err != nil {
|
|
| 134 |
+ fmt.Println(err) |
|
| 135 |
+ os.Exit(1) |
|
| 136 |
+ } |
|
| 137 |
+ if _, err := fmt.Fprintln(w, string(final)); err != nil {
|
|
| 138 |
+ return err |
|
| 139 |
+ } |
|
| 140 |
+ return nil |
|
| 141 |
+} |
|
| 142 |
+ |
|
| 143 |
+func genFlagResult(flags *pflag.FlagSet) []cmdOption {
|
|
| 144 |
+ var result []cmdOption |
|
| 145 |
+ |
|
| 146 |
+ flags.VisitAll(func(flag *pflag.Flag) {
|
|
| 147 |
+ // Todo, when we mark a shorthand is deprecated, but specify an empty message. |
|
| 148 |
+ // The flag.ShorthandDeprecated is empty as the shorthand is deprecated. |
|
| 149 |
+ // Using len(flag.ShorthandDeprecated) > 0 can't handle this, others are ok. |
|
| 150 |
+ if !(len(flag.ShorthandDeprecated) > 0) && len(flag.Shorthand) > 0 {
|
|
| 151 |
+ opt := cmdOption{
|
|
| 152 |
+ Option: flag.Name, |
|
| 153 |
+ Shorthand: flag.Shorthand, |
|
| 154 |
+ DefaultValue: flag.DefValue, |
|
| 155 |
+ Description: forceMultiLine(flag.Usage), |
|
| 156 |
+ } |
|
| 157 |
+ result = append(result, opt) |
|
| 158 |
+ } else {
|
|
| 159 |
+ opt := cmdOption{
|
|
| 160 |
+ Option: flag.Name, |
|
| 161 |
+ DefaultValue: forceMultiLine(flag.DefValue), |
|
| 162 |
+ Description: forceMultiLine(flag.Usage), |
|
| 163 |
+ } |
|
| 164 |
+ result = append(result, opt) |
|
| 165 |
+ } |
|
| 166 |
+ }) |
|
| 167 |
+ |
|
| 168 |
+ return result |
|
| 169 |
+} |
|
| 170 |
+ |
|
| 171 |
+// Temporary workaround for yaml lib generating incorrect yaml with long strings |
|
| 172 |
+// that do not contain \n. |
|
| 173 |
+func forceMultiLine(s string) string {
|
|
| 174 |
+ if len(s) > 60 && !strings.Contains(s, "\n") {
|
|
| 175 |
+ s = s + "\n" |
|
| 176 |
+ } |
|
| 177 |
+ return s |
|
| 178 |
+} |
|
| 179 |
+ |
|
| 180 |
+// Small duplication for cobra utils |
|
| 181 |
+func hasSeeAlso(cmd *cobra.Command) bool {
|
|
| 182 |
+ if cmd.HasParent() {
|
|
| 183 |
+ return true |
|
| 184 |
+ } |
|
| 185 |
+ for _, c := range cmd.Commands() {
|
|
| 186 |
+ if !c.IsAvailableCommand() || c.IsHelpCommand() {
|
|
| 187 |
+ continue |
|
| 188 |
+ } |
|
| 189 |
+ return true |
|
| 190 |
+ } |
|
| 191 |
+ return false |
|
| 192 |
+} |
|
| 193 |
+ |
|
| 194 |
+func parseMDContent(mdString string) (description string, examples string) {
|
|
| 195 |
+ parsedContent := strings.Split(mdString, "\n## ") |
|
| 196 |
+ for _, s := range parsedContent {
|
|
| 197 |
+ if strings.Index(s, "Description") == 0 {
|
|
| 198 |
+ description = strings.Trim(s, "Description\n") |
|
| 199 |
+ } |
|
| 200 |
+ if strings.Index(s, "Examples") == 0 {
|
|
| 201 |
+ examples = strings.Trim(s, "Examples\n") |
|
| 202 |
+ } |
|
| 203 |
+ } |
|
| 204 |
+ return |
|
| 205 |
+} |
|
| 206 |
+ |
|
| 207 |
+type byName []*cobra.Command |
|
| 208 |
+ |
|
| 209 |
+func (s byName) Len() int { return len(s) }
|
|
| 210 |
+func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
| 211 |
+func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
|
| 0 | 212 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,12 @@ |
| 0 |
+#!/usr/bin/env bash |
|
| 1 |
+set -e |
|
| 2 |
+ |
|
| 3 |
+[ -z "$KEEPDEST" ] && \ |
|
| 4 |
+ rm -rf "$DEST" |
|
| 5 |
+ |
|
| 6 |
+( |
|
| 7 |
+ source "${MAKEDIR}/.binary-setup"
|
|
| 8 |
+ export BINARY_SHORT_NAME="yaml-docs-generator" |
|
| 9 |
+ export GO_PACKAGE='github.com/docker/docker/docs/yaml' |
|
| 10 |
+ source "${MAKEDIR}/.binary"
|
|
| 11 |
+) |
| 0 | 12 |
new file mode 100755 |
| ... | ... |
@@ -0,0 +1,19 @@ |
| 0 |
+#!/bin/bash |
|
| 1 |
+ |
|
| 2 |
+if [ -n "${BUILD_DOCS}" ]; then
|
|
| 3 |
+ set -e |
|
| 4 |
+ DOCS_IMAGE=${DOCS_IMAGE:-${IMAGE_NAME}-docs}
|
|
| 5 |
+ docker run \ |
|
| 6 |
+ --entrypoint '' \ |
|
| 7 |
+ --privileged \ |
|
| 8 |
+ -e DOCKER_GITCOMMIT=$(git rev-parse --short HEAD) \ |
|
| 9 |
+ -v $(pwd)/docs/yaml/docs:/docs \ |
|
| 10 |
+ "${IMAGE_NAME}" \
|
|
| 11 |
+ sh -c 'hack/make.sh yaml-docs-generator && bundles/latest/yaml-docs-generator/yaml-docs-generator --target /docs' |
|
| 12 |
+ |
|
| 13 |
+ ( |
|
| 14 |
+ cd docs/yaml |
|
| 15 |
+ docker build -t ${DOCS_IMAGE} .
|
|
| 16 |
+ docker push ${DOCS_IMAGE}
|
|
| 17 |
+ ) |
|
| 18 |
+fi |