From a1c589c53d217bb79c503b5fc904332bb1e7cb43 Mon Sep 17 00:00:00 2001 From: Samuel K Date: Wed, 6 May 2026 05:57:45 -0500 Subject: [PATCH] feat(cli): add --docker-path flag to read-configuration command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow users to specify a custom docker/podman executable path when reading configuration from a running container via --container-id. Previously the command hardcoded "docker" — this aligns it with the exec command which already supports --docker-path. --- cmd/readconfiguration.go | 14 ++- .../readconfiguration/readconfiguration.go | 90 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/cmd/readconfiguration.go b/cmd/readconfiguration.go index 533191c5e..f047a8424 100644 --- a/cmd/readconfiguration.go +++ b/cmd/readconfiguration.go @@ -22,6 +22,7 @@ type ReadConfigurationCmd struct { Config string ContainerID string IDLabels []string + DockerPath string IncludeFeaturesConfiguration bool IncludeMergedConfiguration bool } @@ -49,6 +50,13 @@ func NewReadConfigurationCmd(f *flags.GlobalFlags) *cobra.Command { "", "Read configuration from a running container with the given ID", ) + readConfigCmd.Flags(). + StringVar( + &cmd.DockerPath, + "docker-path", + "", + "Path to the docker/podman executable (defaults to 'docker')", + ) readConfigCmd.Flags(). StringVar( &cmd.Config, @@ -207,7 +215,11 @@ func (cmd *ReadConfigurationCmd) resolveConfigFromContainer(ctx context.Context) string, error, ) { - helper := &docker.DockerHelper{DockerCommand: defaultDockerCommand} + dockerCommand := defaultDockerCommand + if cmd.DockerPath != "" { + dockerCommand = cmd.DockerPath + } + helper := &docker.DockerHelper{DockerCommand: dockerCommand} details, err := helper.InspectContainers(ctx, []string{cmd.ContainerID}) if err != nil { diff --git a/e2e/tests/readconfiguration/readconfiguration.go b/e2e/tests/readconfiguration/readconfiguration.go index be5d720c9..0070be1ec 100644 --- a/e2e/tests/readconfiguration/readconfiguration.go +++ b/e2e/tests/readconfiguration/readconfiguration.go @@ -380,4 +380,94 @@ var _ = ginkgo.Describe("read-configuration command", ginkgo.Label("read-configu gomega.Expect(ok).To(gomega.BeTrue(), "workspace should be an object") gomega.Expect(ws).To(gomega.HaveKey("workspaceFolder")) }, ginkgo.SpecTimeout(framework.TimeoutShort())) + + ginkgo.It("fails with --container-id when container does not exist", + func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") + + _, _, err := f.ExecCommandCapture(ctx, []string{ + "read-configuration", + "--container-id", "nonexistent-container-id-12345", + }) + framework.ExpectError(err) + }, ginkgo.SpecTimeout(framework.TimeoutShort())) + + ginkgo.It("fails with --container-id when container has no metadata label", + func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") + + out, err := exec.CommandContext(ctx, "docker", "run", "-d", + "ghcr.io/devsy-org/test-images/base:ubuntu", + "sleep", "infinity", + ).Output() + framework.ExpectNoError(err) + containerID := strings.TrimSpace(string(out)) + ginkgo.DeferCleanup(func() { + args := []string{"rm", "-f", containerID} + cmd := exec.Command("docker", args...) // #nosec G204 + _ = cmd.Run() + }) + + stdout, _, err := f.ExecCommandCapture(ctx, []string{ + "read-configuration", + "--container-id", containerID, + }) + if err == nil { + var result map[string]any + unmarshalErr := json.Unmarshal([]byte(stdout), &result) + framework.ExpectNoError(unmarshalErr) + config, ok := result["configuration"].(map[string]any) + gomega.Expect(ok).To(gomega.BeTrue()) + gomega.Expect(config).To(gomega.BeEmpty(), + "configuration should be empty when no metadata label exists") + } + }, ginkgo.SpecTimeout(framework.TimeoutShort())) + + ginkgo.It("respects --docker-path flag with valid docker path", + func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") + + metadataLabel := `[{"remoteUser":"dockerpathuser"}]` + out, err := exec.CommandContext(ctx, "docker", "run", "-d", + "--label", "devcontainer.metadata="+metadataLabel, + "ghcr.io/devsy-org/test-images/base:ubuntu", + "sleep", "infinity", + ).Output() + framework.ExpectNoError(err) + containerID := strings.TrimSpace(string(out)) + ginkgo.DeferCleanup(func() { + args := []string{"rm", "-f", containerID} + cmd := exec.Command("docker", args...) // #nosec G204 + _ = cmd.Run() + }) + + stdout, _, err := f.ExecCommandCapture(ctx, []string{ + "read-configuration", + "--container-id", containerID, + "--docker-path", "docker", + }) + framework.ExpectNoError(err) + + var result map[string]any + err = json.Unmarshal([]byte(stdout), &result) + framework.ExpectNoError(err, "output should be valid JSON") + + config, ok := result["configuration"].(map[string]any) + gomega.Expect(ok).To(gomega.BeTrue()) + gomega.Expect(config).To( + gomega.HaveKeyWithValue("remoteUser", "dockerpathuser"), + ) + }, ginkgo.SpecTimeout(framework.TimeoutShort())) + + ginkgo.It("fails with --docker-path pointing to invalid executable", + func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") + + _, _, err := f.ExecCommandCapture(ctx, []string{ + "read-configuration", + "--container-id", "any-container", + "--docker-path", "/nonexistent/path/to/docker", + }) + framework.ExpectError(err) + }, ginkgo.SpecTimeout(framework.TimeoutShort())) })