Skip to content
This repository was archived by the owner on Oct 6, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: validate

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
workflow_dispatch:
push:
branches:
- 'main'
- 'v[0-9]*'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what branching strategy is in place as I don't see any release branch but just in case, I match it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tags are all coming off of main for now (and we're generally backporting those changes to recent releases via modules). We're planning a proper release branch structure starting with 4.43.

tags:
- 'v*'

jobs:
prepare:
runs-on: ubuntu-24.04
outputs:
targets: ${{ steps.generate.outputs.targets }}
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: List targets
id: generate
uses: docker/bake-action/subaction/list-targets@v6
with:
target: validate

validate:
runs-on: ubuntu-24.04
needs:
- prepare
strategy:
fail-fast: false
matrix:
target: ${{ fromJson(needs.prepare.outputs.targets) }}
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
buildkitd-flags: --debug
-
name: Validate
uses: docker/bake-action@v6
with:
targets: ${{ matrix.target }}
49 changes: 49 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# syntax=docker/dockerfile:1

ARG GO_VERSION=1.24
Copy link
Member Author

@crazy-max crazy-max May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use Go latest stable for generating docs but I don't see any Go version set on this repo.

Seems when model-cli is released on CI it uses whatever version is shipped in the runner atm:

- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

This should be fixed imo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. We'll find some spare cycles to address the build stuff in 4.43.

ARG ALPINE_VERSION=3.21

ARG DOCS_FORMATS="md,yaml"

FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
RUN apk add --no-cache rsync git
ENV GOFLAGS=-mod=vendor
ENV CGO_ENABLED=0
WORKDIR /src

FROM base AS docs-gen
WORKDIR /src
RUN --mount=target=. \
--mount=target=/root/.cache,type=cache \
go build -mod=vendor -o /out/docsgen ./docs/generate.go

FROM base AS docs-build
COPY --from=docs-gen /out/docsgen /usr/bin
ENV DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND="model"
ARG DOCS_FORMATS
RUN --mount=target=/context \
--mount=target=.,type=tmpfs <<EOT
set -e
rsync -a /context/. .
docsgen --formats "$DOCS_FORMATS" --source "docs/reference"
mkdir /out
cp -r docs/reference/* /out/
EOT

FROM scratch AS docs-update
COPY --from=docs-build /out /

FROM docs-build AS docs-validate
RUN --mount=target=/context \
--mount=target=.,type=tmpfs <<EOT
set -e
rsync -a /context/. .
git add -A
rm -rf docs/reference/*
cp -rf /out/* ./docs/reference/
if [ -n "$(git status --porcelain -- docs/reference)" ]; then
echo >&2 'ERROR: Docs result differs. Please update with "make docs"'
git status --porcelain -- docs/reference
exit 1
fi
EOT
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: all build clean link mock unit-tests
.PHONY: all build clean link mock unit-tests docs

BINARY_NAME=model-cli

Expand Down Expand Up @@ -67,3 +67,10 @@ clean:
@echo "Cleaning up..."
@rm -f $(BINARY_NAME)
@echo "Cleaned!"

docs:
$(eval $@_TMP_OUT := $(shell mktemp -d -t model-cli-output.XXXXXXXXXX))
docker buildx bake --set "*.output=type=local,dest=$($@_TMP_OUT)" update-docs
rm -rf ./docs/reference/*
cp -R "$($@_TMP_OUT)"/* ./docs/reference/
rm -rf "$($@_TMP_OUT)"/*
15 changes: 15 additions & 0 deletions commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ func NewRootCmd(cli *command.DockerCli) *cobra.Command {
TraverseChildren: plugin.RunningStandalone(),
}

markCommandExperimental(rootCmd)

// Initialize client options and register their flags if running in
// standalone mode.
if plugin.RunningStandalone() {
Expand All @@ -109,3 +111,16 @@ func NewRootCmd(cli *command.DockerCli) *cobra.Command {
)
return rootCmd
}

const annotationExperimentalCLI = "experimentalCLI"

func markCommandExperimental(c *cobra.Command) {
if _, ok := c.Annotations[annotationExperimentalCLI]; ok {
return
}
if c.Annotations == nil {
c.Annotations = make(map[string]string)
}
c.Annotations[annotationExperimentalCLI] = ""
c.Short += " (EXPERIMENTAL)"
}
39 changes: 39 additions & 0 deletions docker-bake.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
variable "GO_VERSION" {
default = null
}
variable "DOCS_FORMATS" {
default = "md,yaml"
}

target "_common" {
args = {
GO_VERSION = GO_VERSION
BUILDKIT_CONTEXT_KEEP_GIT_DIR = 1
}
}

group "default" {
targets = ["validate"]
}

group "validate" {
targets = ["validate-docs"]
}

target "validate-docs" {
inherits = ["_common"]
args = {
DOCS_FORMATS = DOCS_FORMATS
}
target = "docs-validate"
output = ["type=cacheonly"]
}

target "update-docs" {
inherits = ["_common"]
args = {
DOCS_FORMATS = DOCS_FORMATS
}
target = "docs-update"
output = ["./docs/reference"]
}
107 changes: 107 additions & 0 deletions docs/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package main

import (
"log"
"os"
"strings"

clidocstool "github.com/docker/cli-docs-tool"
"github.com/docker/cli/cli/command"
"github.com/docker/model-cli/commands"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

const defaultSourcePath = "docs/reference/"

type options struct {
source string
formats []string
}

func gen(opts *options) error {
log.SetFlags(0)

dockerCLI, err := command.NewDockerCli()
if err != nil {
return err
}
cmd := &cobra.Command{
Use: "docker [OPTIONS] COMMAND [ARG...]",
Short: "The base command for the Docker CLI.",
DisableAutoGenTag: true,
}

cmd.AddCommand(commands.NewRootCmd(dockerCLI))

c, err := clidocstool.New(clidocstool.Options{
Root: cmd,
SourceDir: opts.source,
Plugin: true,
})
if err != nil {
return err
}

for _, format := range opts.formats {
switch format {
case "md":
if err = c.GenMarkdownTree(cmd); err != nil {
return err
}
case "yaml":
fixUpExperimentalCLI(cmd)
if err = c.GenYamlTree(cmd); err != nil {
return err
}
default:
return errors.Errorf("unknown format %q", format)
}
}

return nil
}

func run() error {
opts := &options{}
flags := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError)
flags.StringVar(&opts.source, "source", defaultSourcePath, "Docs source folder")
flags.StringSliceVar(&opts.formats, "formats", []string{}, "Format (md, yaml)")
if err := flags.Parse(os.Args[1:]); err != nil {
return err
}
if len(opts.formats) == 0 {
return errors.New("Docs format required")
}
return gen(opts)
}

func main() {
if err := run(); err != nil {
log.Printf("ERROR: %+v", err)
os.Exit(1)
}
}

// fixUpExperimentalCLI trims the " (EXPERIMENTAL)" suffix from the CLI output,
// as docs.docker.com already displays "experimental (CLI)",
//
// https://github.com/docker/buildx/pull/2188#issuecomment-1889487022
func fixUpExperimentalCLI(cmd *cobra.Command) {
const (
annotationExperimentalCLI = "experimentalCLI"
suffixExperimental = " (EXPERIMENTAL)"
)
if _, ok := cmd.Annotations[annotationExperimentalCLI]; ok {
cmd.Short = strings.TrimSuffix(cmd.Short, suffixExperimental)
}
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if _, ok := f.Annotations[annotationExperimentalCLI]; ok {
f.Usage = strings.TrimSuffix(f.Usage, suffixExperimental)
}
})
for _, c := range cmd.Commands() {
fixUpExperimentalCLI(c)
}
}
40 changes: 40 additions & 0 deletions docs/reference/docker_model.yaml
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@crazy-max do we want the yaml docs to be separate from the markdown ones? (or even; could we generate them dynamically as we did for buildx?).

Mostly considering that these unlikely are useful for readers on GitHub (and the reverse; docs.docker.com doesn't use the markdown ones directly).

Copy link
Member Author

@crazy-max crazy-max May 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I preferred to keep yaml generation in this repo so it's easier to vendor changes on docs repo. So if we want to vendor specific commit for the module we don't need extra work for docs team. Same as compose repo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha; ISTR for compose this caused some complication with other markdown files that still had to be used? Wondering if the YAML should be in a separate directory.

Not a blocker though; mostly something we should probably look at to separate these 🤔

Copy link
Member Author

@crazy-max crazy-max May 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would do the same on buildx repo at some point. Otherwise people need to first clone plugin project, run make docs, copy over generated yaml files in docs repo and update vendor.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a bit why I was considering that as well, because docs only uses the YAML files? But because they're in the same directory would now pull in both; was wondering if putting it separate would allow only pulling in the yaml 🤔 (although possibly it only looks as the module as a whole, and all markdown in all paths?)

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
command: docker model
short: Docker Model Runner
long: Docker Model Runner
pname: docker
plink: docker.yaml
cname:
- docker model inspect
- docker model install-runner
- docker model list
- docker model logs
- docker model package
- docker model pull
- docker model push
- docker model rm
- docker model run
- docker model status
- docker model tag
- docker model uninstall-runner
- docker model version
clink:
- docker_model_inspect.yaml
- docker_model_install-runner.yaml
- docker_model_list.yaml
- docker_model_logs.yaml
- docker_model_package.yaml
- docker_model_pull.yaml
- docker_model_push.yaml
- docker_model_rm.yaml
- docker_model_run.yaml
- docker_model_status.yaml
- docker_model_tag.yaml
- docker_model_uninstall-runner.yaml
- docker_model_version.yaml
deprecated: false
hidden: false
experimental: false
experimentalcli: true
kubernetes: false
swarm: false

26 changes: 26 additions & 0 deletions docs/reference/docker_model_compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
command: docker model compose
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like docker_model_compose.yaml files have been excluded from the cname/clink sections in docker_model.yaml. Does that mean they'll be hidden in the generated output? If so, that's perfect, I think we definitely want to exclude the hidden Compose provider commands. (Same question for markdown, but it looks like they're excluded there too 👍 ?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes seems root compose command is hidden:

c.Hidden = true

$ docker model --help
Usage:  docker model COMMAND

Docker Model Runner

Commands:
  inspect     Display detailed information on one model
  list        List the available models that can be run with the Docker Model Runner
  logs        Fetch the Docker Model Runner logs
  pull        Download a model
  push        Upload a model
  rm          Remove models downloaded from Docker Hub
  run         Run a model with the Docker Model Runner
  status      Check if the Docker Model Runner is running
  tag         Tag a model
  version     Show the Docker Model Runner version

Run 'docker model COMMAND --help' for more information on a command.

But doesn't look inherited:

$ docker model compose --help
Usage:  docker model compose [OPTIONS] COMMAND



Options:
      --project-name string   compose project name

Commands:
  down
  up

Run 'docker model compose COMMAND --help' for more information on a command.

Because Hidden is set after subcommands are added:

c := &cobra.Command{
Use: "compose EVENT",
}
c.AddCommand(newUpCommand())
c.AddCommand(newDownCommand())
c.Hidden = true

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok seems cli-docs-tool doesn't handle inheritance for hidden commands so subcommands docs is still generated. I marked all subcommands as hidden in the meantime.

@thaJeztah Wonder if we should handle this, WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... good one; so ISTR we had some discussion around hidden commands (and to generate things regardless, leaving it to the docs.docker.com repository to decide whether to publish docs for them?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@crazy-max crazy-max May 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to cli-docs-tool v0.10.0 to fix this issue

pname: docker model
plink: docker_model.yaml
cname:
- docker model compose down
- docker model compose up
clink:
- docker_model_compose_down.yaml
- docker_model_compose_up.yaml
options:
- option: project-name
value_type: string
description: compose project name
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
deprecated: false
hidden: true
experimental: false
experimentalcli: true
kubernetes: false
swarm: false

32 changes: 32 additions & 0 deletions docs/reference/docker_model_compose_down.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
command: docker model compose down
usage: docker model compose down
pname: docker model compose
plink: docker_model_compose.yaml
options:
- option: model
value_type: stringArray
default_value: '[]'
description: model to use
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
inherited_options:
- option: project-name
value_type: string
description: compose project name
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
deprecated: false
hidden: true
experimental: false
experimentalcli: true
kubernetes: false
swarm: false

Loading