Skip to content

Commit

Permalink
Support combination of no-remote and experimental CLI
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Ferquel <[email protected]>
  • Loading branch information
simonferquel committed Mar 18, 2019
1 parent ac2752a commit 427e193
Showing 1 changed file with 110 additions and 47 deletions.
157 changes: 110 additions & 47 deletions cmd/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,71 +337,101 @@ func findCommand(cmd *cobra.Command, commands []string) bool {
}

func isSupported(cmd *cobra.Command, details versionDetails) error {
if isNoRemote(cmd) {
return nil
}
if err := areSubcommandsSupported(cmd, details); err != nil {
return err
}
return areFlagsSupported(cmd, details)
}

func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
clientVersion := details.Client().ClientVersion()
osType := details.ServerInfo().OSType
hasExperimental := details.ServerInfo().HasExperimental
hasExperimentalCLI := details.ClientInfo().HasExperimental
var checks []flagCheck
if !details.ClientInfo().HasExperimental {
checks = append(checks, flagAnnotationCheck("experimentalCLI", func(f *pflag.Flag) error {
return fmt.Errorf("\"--%s\" is on a Docker cli with experimental cli features enabled", f.Name)
}))
}

errs := []string{}
if !isLocalOnly(cmd) {
clientVersion := details.Client().ClientVersion()
osType := details.ServerInfo().OSType
hasExperimental := details.ServerInfo().HasExperimental
checks = append(checks,
flagAnnotationCheck("version", func(f *pflag.Flag) error {
if !isVersionSupported(f, clientVersion) {
return fmt.Errorf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagAnnotation(f, "version"), clientVersion)
}
return nil
}),
flagAnnotationCheck("ostype", func(f *pflag.Flag) error {
if !isOSTypeSupported(f, osType) {
return fmt.Errorf("\"--%s\" is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s", f.Name, getFlagAnnotation(f, "ostype"), osType)
}
return nil
}),
)
if !hasExperimental {
checks = append(checks, flagAnnotationCheck("experimental", func(f *pflag.Flag) error {
return fmt.Errorf("\"--%s\" is only supported on a Docker daemon with experimental features enabled", f.Name)
}))
}
}

cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Changed {
if !isVersionSupported(f, clientVersion) {
errs = append(errs, fmt.Sprintf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagAnnotation(f, "version"), clientVersion))
return
}
if !isOSTypeSupported(f, osType) {
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s", f.Name, getFlagAnnotation(f, "ostype"), osType))
return
}
if _, ok := f.Annotations["experimental"]; ok && !hasExperimental {
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon with experimental features enabled", f.Name))
}
if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
errs = append(errs, fmt.Sprintf("\"--%s\" is on a Docker cli with experimental cli features enabled", f.Name))
}
// buildkit-specific flags are noop when buildkit is not enabled, so we do not add an error in that case
return checkFlags(cmd, checks)
}

func flagAnnotationCheck(annotationName string, check func(*pflag.Flag) error) flagCheck {
return func(f *pflag.Flag) error {
if _, ok := f.Annotations[annotationName]; ok {
return check(f)
}
})
if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
return nil
}
return nil
}

// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error {
clientVersion := details.Client().ClientVersion()
osType := details.ServerInfo().OSType
hasExperimental := details.ServerInfo().HasExperimental
hasExperimentalCLI := details.ClientInfo().HasExperimental
var checks []commandCheck
if !details.ClientInfo().HasExperimental {
checks = append(checks, commandAnnotationCheck("experimentalCLI", func(_ string) error {
return fmt.Errorf("%s is only supported on a Docker cli with experimental cli features enabled", cmd.CommandPath())
}))
}

// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
for curr := cmd; curr != nil; curr = curr.Parent() {
if cmdVersion, ok := curr.Annotations["version"]; ok && versions.LessThan(clientVersion, cmdVersion) {
return fmt.Errorf("%s requires API version %s, but the Docker daemon API version is %s", cmd.CommandPath(), cmdVersion, clientVersion)
}
if os, ok := curr.Annotations["ostype"]; ok && os != osType {
return fmt.Errorf("%s is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s", cmd.CommandPath(), os, osType)
}
if _, ok := curr.Annotations["experimental"]; ok && !hasExperimental {
return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
if !isLocalOnly(cmd) {
clientVersion := details.Client().ClientVersion()
osType := details.ServerInfo().OSType
hasExperimental := details.ServerInfo().HasExperimental
checks = append(checks,
commandAnnotationCheck("version", func(cmdVersion string) error {
if versions.LessThan(clientVersion, cmdVersion) {
return fmt.Errorf("%s requires API version %s, but the Docker daemon API version is %s", cmd.CommandPath(), cmdVersion, clientVersion)
}
return nil
}),
commandAnnotationCheck("ostype", func(os string) error {
if os != osType {
return fmt.Errorf("%s is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s", cmd.CommandPath(), os, osType)
}
return nil
}),
)
if !hasExperimental {
checks = append(checks, commandAnnotationCheck("experimental", func(_ string) error {
return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
}))
}
if _, ok := curr.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
return fmt.Errorf("%s is only supported on a Docker cli with experimental cli features enabled", cmd.CommandPath())
}

return checkCommandRecursively(cmd, checks)
}

func commandAnnotationCheck(annotationName string, check func(string) error) commandCheck {
return func(cmd *cobra.Command) error {
if value, ok := cmd.Annotations[annotationName]; ok {
return check(value)
}
return nil
}
return nil
}

func getFlagAnnotation(f *pflag.Flag, annotation string) string {
Expand Down Expand Up @@ -436,7 +466,7 @@ func hasTags(cmd *cobra.Command) bool {
return false
}

func isNoRemote(cmd *cobra.Command) bool {
func isLocalOnly(cmd *cobra.Command) bool {
for cmd != nil {
if _, ok := cmd.Annotations["no-remote"]; ok {
return true
Expand All @@ -446,3 +476,36 @@ func isNoRemote(cmd *cobra.Command) bool {

return false
}

func checkCommandRecursively(cmd *cobra.Command, checks []commandCheck) error {
for cmd != nil {
for _, check := range checks {
if err := check(cmd); err != nil {
return err
}
}
cmd = cmd.Parent()
}
return nil
}

func checkFlags(cmd *cobra.Command, checks []flagCheck) error {
var errs []string
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Changed {
for _, check := range checks {
if err := check(f); err != nil {
errs = append(errs, err.Error())
}
}
}
})
if len(errs) == 0 {
return nil
}
return errors.New(strings.Join(errs, "\n"))
}

type commandCheck func(cmd *cobra.Command) error

type flagCheck func(f *pflag.Flag) error

0 comments on commit 427e193

Please sign in to comment.