From 04f28be8445d6f163d717c8adc5eb429cf7b2601 Mon Sep 17 00:00:00 2001 From: Jye Cusch Date: Mon, 4 Nov 2024 15:47:32 +1100 Subject: [PATCH] create dependency type to allow for composition --- cmd/build.go | 2 +- cmd/run.go | 2 +- cmd/stack.go | 2 +- pkg/docker/docker.go | 6 +- pkg/view/tui/dependency.go | 151 ++++++++++++++++++++++++------------- 5 files changed, 105 insertions(+), 58 deletions(-) diff --git a/cmd/build.go b/cmd/build.go index bf777eb1..bd8fe08d 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -48,5 +48,5 @@ var buildCmd = &cobra.Command{ } func init() { - rootCmd.AddCommand(tui.AddDependencyCheck(buildCmd, &tui.ContainerToolDependency{})) + rootCmd.AddCommand(tui.AddDependencyCheck(buildCmd, tui.RequireContainerBuilder)) } diff --git a/cmd/run.go b/cmd/run.go index b0600536..c1e061d4 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -237,5 +237,5 @@ func init() { false, "disable browser opening for local dashboard, note: in CI mode the browser opening feature is disabled", ) - rootCmd.AddCommand(tui.AddDependencyCheck(runCmd, &tui.ContainerToolDependency{})) + rootCmd.AddCommand(tui.AddDependencyCheck(runCmd, tui.RequireContainerBuilder)) } diff --git a/cmd/stack.go b/cmd/stack.go index dd8e69f8..d27f918b 100644 --- a/cmd/stack.go +++ b/cmd/stack.go @@ -610,7 +610,7 @@ func init() { newStackCmd.Flags().BoolVarP(&forceNewStack, "force", "f", false, "force stack creation.") // Update Stack (Up) - stackCmd.AddCommand(tui.AddDependencyCheck(stackUpdateCmd, &tui.ContainerToolDependency{})) + stackCmd.AddCommand(tui.AddDependencyCheck(stackUpdateCmd, tui.RequireContainerBuilder)) stackUpdateCmd.Flags().StringVarP(&envFile, "env-file", "e", "", "--env-file config/.my-env") stackUpdateCmd.Flags().BoolVarP(&forceStack, "force", "f", false, "force override previous deployment") tui.CheckErr(AddOptions(stackUpdateCmd, false)) diff --git a/pkg/docker/docker.go b/pkg/docker/docker.go index 08539487..497299a4 100644 --- a/pkg/docker/docker.go +++ b/pkg/docker/docker.go @@ -107,7 +107,7 @@ func (d *Docker) Build(dockerfile, srcPath, imageTag string, buildArgs map[strin // If docker is available, create a buildx builder var builder *BuildxBuilder - if a, _ := tui.DockerAvailable(); a { + if err := tui.DockerAvailable(); err == nil { var err error builder, err = d.createBuildxBuilder() @@ -187,8 +187,8 @@ func (d *Docker) Build(dockerfile, srcPath, imageTag string, buildArgs map[strin // The args should be compatible with either docker or podman baseCommand := "docker" - if a, _ := tui.DockerAvailable(); !a { - if b, _ := tui.PodmanAvailable(); b { + if err := tui.DockerAvailable(); err != nil { + if err := tui.PodmanAvailable(); err == nil { baseCommand = "podman" } else { return errors.New("Docker or Podman is required, see https://docs.docker.com/engine/install/ for docker installation instructions") diff --git a/pkg/view/tui/dependency.go b/pkg/view/tui/dependency.go index ea778365..5ba755a1 100644 --- a/pkg/view/tui/dependency.go +++ b/pkg/view/tui/dependency.go @@ -17,94 +17,152 @@ package tui import ( - "errors" "os/exec" "github.com/spf13/cobra" ) +type DependencyError struct { + details string + assist string +} + +func (d *DependencyError) Error() string { + return d.details +} + +func (d *DependencyError) Assist() string { + return d.assist +} + +type Dependency = func() *DependencyError + +func atLeastOne(d ...Dependency) Dependency { + return func() *DependencyError { + if len(d) == 0 { + return nil + } + + // Extract the preference dependency + primary := d[0]() + if primary == nil { + return nil + } + + // Check the rest of the dependencies + for _, dep := range d[1:] { + if dep() == nil { + return nil + } + } + + return primary + } +} + var ( dockerAvailable bool dockerBuildxAvailable bool podmanAvailable bool ) -func DockerAvailable() (bool, error) { +func DockerAvailable() error { if dockerAvailable { - return true, nil + return nil } err := exec.Command("docker", "version").Run() - if err == nil { - dockerAvailable = true + if err != nil { + return err } - return dockerAvailable, err + dockerAvailable = true + + return nil } -func DockerBuildxAvailable() (bool, error) { +func DockerBuildxAvailable() error { if dockerBuildxAvailable { - return true, nil + return nil } err := exec.Command("docker", "buildx", "version").Run() - if err == nil { - dockerBuildxAvailable = true + if err != nil { + return err } - return dockerBuildxAvailable, err + dockerBuildxAvailable = true + + return nil } -func PodmanAvailable() (bool, error) { +func PodmanAvailable() error { if podmanAvailable { - return true, nil + return nil } err := exec.Command("podman", "version").Run() - if err == nil { - podmanAvailable = true + if err != nil { + return err } - return podmanAvailable, err -} + podmanAvailable = true -type Dependency interface { - // Check if the dependency is met - Check() error - // If the dependency is not met, provide a message to the user to assist in installing it - Assist() string + return nil } -type ContainerToolDependency struct { - message string -} +func RequireDocker() *DependencyError { + if dockerAvailable { + return nil + } -func (c *ContainerToolDependency) Check() error { - _, err := DockerAvailable() + err := DockerAvailable() if err != nil { - _, podErr := PodmanAvailable() - if podErr != nil { - c.message = "Docker or Podman is required, see https://docs.docker.com/engine/install/ for docker installation instructions" - return err - } else { - // Use podman - return nil + depErr := DependencyError{ + details: err.Error(), + assist: "Docker is required, see https://docs.docker.com/engine/install/ for docker installation instructions", } + + return &depErr } - _, err = DockerBuildxAvailable() + err = DockerBuildxAvailable() if err != nil { - c.message = "docker buildx is required to run this command. For installation instructions see: https://github.com/docker/buildx" - return err + depErr := DependencyError{ + details: err.Error(), + assist: "docker buildx is required to run this command. For installation instructions see: https://github.com/docker/buildx", + } + + return &depErr } + dockerBuildxAvailable = true + return nil } -func (c *ContainerToolDependency) Assist() string { - return c.message +func RequirePodman() *DependencyError { + if podmanAvailable { + return nil + } + + err := PodmanAvailable() + if err != nil { + depErr := DependencyError{ + details: err.Error(), + assist: "Podman is required, see https://docs.docker.com/engine/install/ for docker installation instructions", + } + + return &depErr + } + + podmanAvailable = true + + return nil } +var RequireContainerBuilder = atLeastOne(RequireDocker, RequirePodman) + // AddDependencyCheck - Wraps a cobra command with a pre-run that // will check for dependencies func AddDependencyCheck(cmd *cobra.Command, deps ...Dependency) *cobra.Command { @@ -121,21 +179,10 @@ func checkDependencies(deps ...Dependency) error { return nil } - missing := make([]Dependency, 0) - for _, p := range deps { - err := p.Check() + err := p() if err != nil { - missing = append(missing, p) - } - } - - if len(missing) > 0 { - for _, p := range missing { - err := errors.New(p.Assist()) - if err != nil { - return err - } + return err } }