diff --git a/README.md b/README.md index 354708c..a870bcc 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Spin applications have the ability to export metrics and trace data. This plugin This plugin relies on third-party software to work properly. Please be sure you have the following installed before continuing: -- Latest version of [Docker](https://www.docker.com/products/docker-desktop) +- Latest version of [Docker](https://www.docker.com/products/docker-desktop) or [Podman](https://podman.io/docs) # Installation @@ -49,7 +49,7 @@ The `otel` plugin currently supports two different observability stacks: - Default: Multi-container observability stack based on Prometheus, Loki, Grafana and Jaeger - Aspire: Single-container observability stack using .NET Aspire Dashboard - + # Usage Once the plugin is installed, you can try the below commands: diff --git a/cmd/cleanup.go b/cmd/cleanup.go index 4eb80ff..4439200 100644 --- a/cmd/cleanup.go +++ b/cmd/cleanup.go @@ -26,12 +26,12 @@ func init() { cleanUpCmd.Flags().BoolVarP(&removeContainers, "remove", "r", false, "If specified, OTel containers will be removed") } -func getIDs(dockerOutput []byte) []string { +func getIDs(runtimeOutput []byte) []string { var result []string - outputArray := strings.Split(string(dockerOutput), "\n") + outputArray := strings.Split(string(runtimeOutput), "\n") // skip the first line of the output, because it is the table header row - // docker ps does not support hiding column headers + // {{runtime}} ps does not support hiding column headers for _, entry := range outputArray[1:] { fields := strings.Fields(entry) if len(fields) > 0 { @@ -43,21 +43,22 @@ func getIDs(dockerOutput []byte) []string { } func cleanUp() error { - if err := checkDocker(); err != nil { + runtime, err := detectContainerRuntime() + if err != nil { return err } - fmt.Println("Stopping Spin OpenTelemetry Docker containers...") - getContainers := exec.Command("docker", "ps", fmt.Sprintf("--filter=name=%s*", otelConfigDirName), "--format=table") - dockerPsOutput, err := getContainers.CombinedOutput() + fmt.Println("Stopping Spin OpenTelemetry containers...") + getContainers := exec.Command(runtime, "ps", fmt.Sprintf("--filter=name=%s*", otelConfigDirName)) + processOutput, err := getContainers.CombinedOutput() if err != nil { - fmt.Println(string(dockerPsOutput)) + fmt.Println(string(processOutput)) return err } - containerIDs := getIDs(dockerPsOutput) + containerIDs := getIDs(processOutput) - // The `docker stop` command will throw an error if there are no containers to stop + // The `{{runtime}} stop` command will throw an error if there are no containers to stop if len(containerIDs) == 0 { fmt.Println("No Spin OpenTelemetry resources found. Nothing to clean up.") return nil @@ -67,7 +68,7 @@ func cleanUp() error { if removeContainers { cleanupArgs = []string{"rm", "-f"} } - stopContainers := exec.Command("docker", append(cleanupArgs, containerIDs...)...) + stopContainers := exec.Command(runtime, append(cleanupArgs, containerIDs...)...) stopContainersOutput, err := stopContainers.CombinedOutput() if err != nil { fmt.Println(string(stopContainersOutput)) diff --git a/cmd/root.go b/cmd/root.go index d4b6d20..2e721d8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path" + "strings" open "github.com/fermyon/otel-plugin/cmd/open" "github.com/spf13/cobra" @@ -35,19 +36,51 @@ func setOtelConfigPath() error { return nil } -// checkDocker checks whether Docker is installed and the Docker daemon is running -func checkDocker() error { - cmd := exec.Command("docker", "--version") - if err := cmd.Run(); err != nil { - return fmt.Errorf("Docker appears not to be installed, so please visit their install page and try again once installed: https://www.docker.com/products/docker-desktop") +// detectContainerRuntime checks for a supported container runtime. +// +// Returns the detected runtime name. +func detectContainerRuntime() (string, error) { + // Default Docker, fallback Podman + runtimesToCheck := []string{"docker", "podman"} + runtime := "" + for _, rt := range runtimesToCheck { + cmd := exec.Command(rt, "--version") + if err := cmd.Run(); err != nil { + continue + } + runtime = rt + break + } + + if runtime == "" { + return "", fmt.Errorf("Unable to detect container runtime.\nPlease ensure at least one of the following is installed and in the PATH: %s\n", strings.Join(runtimesToCheck, ", ")) + } + + if runtime == "podman" { + // Podman doesn't auto-install a compose provider + composeProvidersToCheck := []string{"podman-compose", "docker-compose"} + composeProvider := "" + for _, rt := range composeProvidersToCheck { + cmd := exec.Command(rt, "--version") + if err := cmd.Run(); err != nil { + continue + } + + composeProvider = rt + break + } + + if composeProvider == "" { + return "", fmt.Errorf("Unable to detect compose provider.\n Please ensure at least one of the following is installed and in the PATH: %s\n", strings.Join(composeProvidersToCheck, ", ")) + } } - cmd = exec.Command("docker", "info") + cmd := exec.Command(runtime, "info") if err := cmd.Run(); err != nil { - return fmt.Errorf("The Docker daemon appears not to be running. The command to start Docker depends on your operating system. For instructions, check the correct page under https://docs.docker.com/engine/install") + return "", fmt.Errorf("The daemon/virtual machine for %[1]s appears not to be running. Please check the documentation for %[1]s for more information.", runtime) } - return nil + return runtime, nil } func Execute() { diff --git a/cmd/setup.go b/cmd/setup.go index df0aec0..1ff5b0f 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -14,7 +14,8 @@ var ( aspire = false setUpCmd = &cobra.Command{ Use: "setup", - Short: "Run OpenTelemetry dependencies in Docker.", + Short: "Run OpenTelemetry dependencies as containers", + Long: "Required OpenTelemetry dependencies will be started as containers using a supported container runtime (docker, podman)", RunE: func(cmd *cobra.Command, args []string) error { s := stack.GetStackByFlags(aspire) if err := setUp(s); err != nil { @@ -30,16 +31,18 @@ func init() { } func setUp(s stack.Stack) error { - if err := checkDocker(); err != nil { + runtime, err := detectContainerRuntime() + if err != nil { return err } + composeFileName := s.GetComposeFileName() composeFilePath := path.Join(otelConfigPath, composeFileName) if _, err := os.Stat(composeFilePath); os.IsNotExist(err) { return fmt.Errorf("The \"otel-config\" directory is missing the \"%s\" file, so please consider removing and re-installing the otel plugin", composeFileName) } - cmd := exec.Command("docker", "compose", "-f", composeFilePath, "up", "-d") + cmd := exec.Command(runtime, "compose", "-f", composeFilePath, "up", "-d") fmt.Println("Pulling and running Spin OpenTelemetry resources...") diff --git a/cmd/up.go b/cmd/up.go index 6fcdc0e..340577e 100644 --- a/cmd/up.go +++ b/cmd/up.go @@ -22,10 +22,6 @@ var upCmd = &cobra.Command{ } func up(args []string) error { - if err := checkDocker(); err != nil { - return err - } - pathToSpin := os.Getenv("SPIN_BIN_PATH") if pathToSpin == "" { return fmt.Errorf("Please ensure that you are running \"spin otel up\", rather than calling the OpenTelemetry plugin binary directly")